98_WeekdayTimer.pm 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272
  1. # $Id: 98_WeekdayTimer.pm 13374 2017-02-09 20:00:35Z orti-otto $
  2. ##############################################################################
  3. #
  4. # 98_WeekdayTimer.pm
  5. # written by Dietmar Ortmann
  6. # modified by Tobias Faust
  7. #
  8. # This file is part of fhem.
  9. #
  10. # Fhem is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Fhem is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. ##############################################################################
  24. package main;
  25. use strict;
  26. use warnings;
  27. use POSIX;
  28. use Time::Local 'timelocal_nocheck';
  29. use Data::Dumper;
  30. $Data::Dumper::Sortkeys = 1;
  31. ################################################################################
  32. sub WeekdayTimer_Initialize($){
  33. my ($hash) = @_;
  34. if(!$modules{Twilight}{LOADED} && -f "$attr{global}{modpath}/FHEM/59_Twilight.pm") {
  35. my $ret = CommandReload(undef, "59_Twilight");
  36. Log3 undef, 1, $ret if($ret);
  37. }
  38. # Consumer
  39. $hash->{SetFn} = "WeekdayTimer_Set";
  40. $hash->{DefFn} = "WeekdayTimer_Define";
  41. $hash->{UndefFn} = "WeekdayTimer_Undef";
  42. $hash->{GetFn} = "WeekdayTimer_Get";
  43. $hash->{AttrFn} = "WeekdayTimer_Attr";
  44. $hash->{UpdFn} = "WeekdayTimer_Update";
  45. $hash->{AttrList}= "disable:0,1 delayedExecutionCond switchInThePast:0,1 commandTemplate ".
  46. $readingFnAttributes;
  47. }
  48. ################################################################################
  49. sub WeekdayTimer_InitHelper($) {
  50. my ($hash) = @_;
  51. $hash->{longDays} = { "de" => ["Sonntag", "Montag","Dienstag","Mittwoch", "Donnerstag","Freitag", "Samstag", "Wochenende", "Werktags" ],
  52. "en" => ["Sunday", "Monday","Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "weekend", "weekdays" ],
  53. "fr" => ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi","Samedi", "weekend", "jours de la semaine"]};
  54. $hash->{shortDays} = { "de" => ["so", "mo", "di", "mi", "do", "fr", "sa", '$we', '!$we' ],
  55. "en" => ["su", "mo", "tu", "we", "th", "fr", "sa", '$we', '!$we' ],
  56. "fr" => ["di", "lu", "ma", "me", "je", "ve", "sa", '$we', '!$we' ]};
  57. }
  58. ################################################################################
  59. sub WeekdayTimer_Set($@) {
  60. my ($hash, @a) = @_;
  61. return "no set value specified" if(int(@a) < 2);
  62. return "Unknown argument $a[1], choose one of enable disable " if($a[1] eq "?");
  63. my $name = shift @a;
  64. my $v = join(" ", @a);
  65. Log3 $hash, 3, "[$name] set $name $v";
  66. if ($v eq "enable") {
  67. fhem("attr $name disable 0");
  68. } elsif ($v eq "disable") {
  69. fhem("attr $name disable 1");
  70. }
  71. return undef;
  72. }
  73. ################################################################################
  74. sub WeekdayTimer_Get($@) {
  75. my ($hash, @a) = @_;
  76. return "argument is missing" if(int(@a) != 2);
  77. $hash->{LOCAL} = 1;
  78. delete $hash->{LOCAL};
  79. my $reading= $a[1];
  80. my $value;
  81. if(defined($hash->{READINGS}{$reading})) {
  82. $value= $hash->{READINGS}{$reading}{VAL};
  83. } else {
  84. return "no such reading: $reading";
  85. }
  86. return "$a[0] $reading => $value";
  87. }
  88. ################################################################################
  89. sub WeekdayTimer_Undef($$) {
  90. my ($hash, $arg) = @_;
  91. foreach my $idx (keys %{$hash->{profil}}) {
  92. myRemoveInternalTimer($idx, $hash);
  93. }
  94. myRemoveInternalTimer("SetTimerOfDay", $hash);
  95. delete $modules{$hash->{TYPE}}{defptr}{$hash->{NAME}};
  96. return undef;
  97. }
  98. ################################################################################
  99. sub WeekdayTimer_Define($$) {
  100. my ($hash, $def) = @_;
  101. WeekdayTimer_InitHelper($hash);
  102. my @a = split("[ \t]+", $def);
  103. return "Usage: define <name> $hash->{TYPE} <device> <language> <switching times> <condition|command>"
  104. if(@a < 4);
  105. #fuer den modify Altlasten bereinigen
  106. delete($hash->{helper});
  107. my $name = shift @a;
  108. my $type = shift @a;
  109. my $device = shift @a;
  110. WeekdayTimer_DeleteTimer($hash);
  111. my $delVariables = "(CONDITION|COMMAND|profile|Profil)";
  112. map { delete $hash->{$_} if($_=~ m/^$delVariables.*/g) } keys %{$hash};
  113. my $language = WeekdayTimer_Language ($hash, \@a);
  114. my $idx = 0;
  115. $hash->{dayNumber} = {map {$_ => $idx++} @{$hash->{shortDays}{$language}}};
  116. $hash->{helper}{daysRegExp} = '(' . join ("|", @{$hash->{shortDays}{$language}}) . ")";
  117. $hash->{helper}{daysRegExpMessage} = $hash->{helper}{daysRegExp};
  118. $hash->{helper}{daysRegExp} =~ s/\$/\\\$/g;
  119. $hash->{helper}{daysRegExp} =~ s/\!/\\\!/g;
  120. WeekdayTimer_GlobalDaylistSpec ($hash, \@a);
  121. $hash->{NAME} = $name;
  122. $hash->{DEVICE} = $device;
  123. my @switchingtimes = WeekdayTimer_gatherSwitchingTimes ($hash, \@a);
  124. my $conditionOrCommand = join (" ", @a);
  125. # test if device is defined
  126. Log3 ($hash, 3, "[$name] device <$device> in fhem not defined, but accepted") if(!$defs{$device});
  127. # wenn keine switchintime angegeben ist, dann Fehler
  128. Log3 ($hash, 3, "[$name] no valid Switchingtime found in <$conditionOrCommand>, check first parameter") if (@switchingtimes == 0);
  129. $hash->{STILLDONETIME} = 0;
  130. $hash->{SWITCHINGTIMES} = \@switchingtimes;
  131. $attr{$name}{verbose} = 5 if (!defined $attr{$name}{verbose} && $name =~ m/^tst.*/ );
  132. $defs{$device}{STILLDONETIME} = 0 if($defs{$device});
  133. $modules{$hash->{TYPE}}{defptr}{$hash->{NAME}} = $hash;
  134. $hash->{CONDITION} = ""; $hash->{COMMAND} = "";
  135. if($conditionOrCommand =~ m/^\(.*\)$/g) { #condition (*)
  136. $hash->{CONDITION} = $conditionOrCommand;
  137. } elsif(length($conditionOrCommand) > 0 ) {
  138. $hash->{COMMAND} = $conditionOrCommand;
  139. }
  140. WeekdayTimer_Profile ($hash);
  141. delete $hash->{VERZOEGRUNG};
  142. delete $hash->{VERZOEGRUNG_IDX};
  143. $attr{$name}{commandTemplate} =
  144. 'set $NAME '. WeekdayTimer_isHeizung($hash) .' $EVENT' if (!defined $attr{$name}{commandTemplate});
  145. InternalTimer(time(), "$hash->{TYPE}_SetTimer", $hash, 0);
  146. WeekdayTimer_SetTimerForMidnightUpdate( { HASH => $hash} );
  147. return undef;
  148. }
  149. ################################################################################
  150. sub WeekdayTimer_Profile($) {
  151. my $hash = shift;
  152. my $language = $hash->{LANGUAGE};
  153. my %longDays = %{$hash->{longDays}};
  154. delete $hash->{profil};
  155. my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time());
  156. my $now = time();
  157. # ---- Zeitpunkte den Tagen zuordnen -----------------------------------
  158. my $idx = 0;
  159. foreach my $st (@{$hash->{SWITCHINGTIMES}}) {
  160. my ($tage,$time,$parameter) = WeekdayTimer_SwitchingTime ($hash, $st);
  161. $idx++;
  162. foreach my $d (@{$tage}) {
  163. my @listeDerTage = ($d);
  164. push (@listeDerTage, WeekdayTimer_getListeDerTage($d, $time)) if ($d>=7);
  165. map { my $day = $_;
  166. my $dayOfEchteZeit = $day;
  167. $dayOfEchteZeit = ($wday>=1&&$wday<=5) ? 6 : $wday if ($day==7); # ggf. Samstag $wday ~~ [1..5]
  168. $dayOfEchteZeit = ($wday==0||$wday==6) ? 1 : $wday if ($day==8); # ggf. Montag $wday ~~ [0, 6]
  169. my $echtZeit = WeekdayTimer_EchteZeit($hash, $dayOfEchteZeit, $time);
  170. $hash->{profile} {$day}{$echtZeit} = $parameter;
  171. $hash->{profile_IDX}{$day}{$echtZeit} = $idx;
  172. } @listeDerTage;
  173. }
  174. }
  175. # ---- Zeitpunkte des aktuellen Tages mit EPOCH ermitteln --------------
  176. $idx = 0;
  177. foreach my $st (@{$hash->{SWITCHINGTIMES}}) {
  178. my ($tage,$time,$parameter) = WeekdayTimer_SwitchingTime ($hash, $st);
  179. my $echtZeit = WeekdayTimer_EchteZeit ($hash, $wday, $time);
  180. my ($stunde, $minute, $sekunde) = split (":",$echtZeit);
  181. $idx++;
  182. $hash->{profil}{$idx}{TIME} = $time;
  183. $hash->{profil}{$idx}{PARA} = $parameter;
  184. $hash->{profil}{$idx}{EPOCH} = WeekdayTimer_zeitErmitteln ($now, $stunde, $minute, $sekunde, 0);
  185. $hash->{profil}{$idx}{TAGE} = $tage;
  186. }
  187. # ---- Texte Readings aufbauen -----------------------------------------
  188. Log3 $hash, 4, "[$hash->{NAME}] " . sunrise_abs() . " " . sunset_abs() . " " . $longDays{$language}[$wday];
  189. foreach my $d (sort keys %{$hash->{profile}}) {
  190. my $profiltext = "";
  191. foreach my $t (sort keys %{$hash->{profile}{$d}}) {
  192. $profiltext .= "$t " . $hash->{profile}{$d}{$t} . ", ";
  193. }
  194. my $profilKey = "Profil $d: $longDays{$language}[$d]";
  195. $profiltext =~ s/, $//;
  196. $hash->{$profilKey} = $profiltext;
  197. Log3 $hash, 4, "[$hash->{NAME}] $profiltext ($profilKey)";
  198. }
  199. # für logProxy umhaengen
  200. $hash->{helper}{SWITCHINGTIME} = $hash->{profile};
  201. delete $hash->{profile};
  202. }
  203. ################################################################################
  204. sub WeekdayTimer_getListeDerTage($$) {
  205. my ($d, $time) = @_;
  206. my %hdays=();
  207. @hdays{(0, 6)} = undef if ($d==7); # sa,so ( $we)
  208. @hdays{(1..5)} = undef if ($d==8); # mo-fr (!$we)
  209. my $wday;
  210. my $now = time();
  211. my ($sec,$min,$hour,$mday,$mon,$year,$nowWday,$yday,$isdst) = localtime($now);
  212. my @realativeWdays = (0..6);
  213. for (my $i=0;$i<=6;$i++) {
  214. my $relativeDay = $i-$nowWday;
  215. #Log 3, "relativeDay------------>$relativeDay";
  216. my ($stunde, $minute, $sekunde) = split (":",$time);
  217. my $echteZeit = WeekdayTimer_zeitErmitteln ($now, $stunde, $minute, $sekunde, $relativeDay);
  218. #Log 3, "echteZeit---$i---->>>$relativeDay<<<----->".FmtDateTime($echteZeit);
  219. ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($echteZeit);
  220. my $h2we = $attr{global}{holiday2we};
  221. if($h2we) {
  222. my $ergebnis = fhem("get $h2we ".sprintf("%02d-%02d",$mon+1,$mday),1);
  223. if ($ergebnis ne "none") {
  224. #Log 3, "ergebnis-------$i----->$ergebnis";
  225. $hdays{$i} = undef if ($d==7); # $we Tag aufnehmen
  226. delete $hdays{$i} if ($d==8); # !$we Tag herausnehmen
  227. }
  228. }
  229. }
  230. #Log 3, "result------------>" . join (" ", sort keys %hdays);
  231. return keys %hdays;
  232. }
  233. ################################################################################
  234. sub WeekdayTimer_SwitchingTime($$) {
  235. my ($hash, $switchingtime) = @_;
  236. my $name = $hash->{NAME};
  237. my $globalDaylistSpec = $hash->{GlobalDaylistSpec};
  238. my @tageGlobal = @{WeekdayTimer_daylistAsArray($hash, $globalDaylistSpec)};
  239. my (@st, $daylist, $time, $timeString, $para);
  240. @st = split(/\|/, $switchingtime);
  241. if ( @st == 2) {
  242. $daylist = ($globalDaylistSpec gt "") ? $globalDaylistSpec : "0123456";
  243. $time = $st[0];
  244. $para = $st[1];
  245. } elsif ( @st == 3) {
  246. $daylist = $st[0];
  247. $time = $st[1];
  248. $para = $st[2];
  249. }
  250. my @tage = @{WeekdayTimer_daylistAsArray($hash, $daylist)};
  251. my $tage=@tage;
  252. if ( $tage==0 ) {
  253. Log3 ($hash, 1, "[$name] invalid daylist in $name <$daylist> use one of 012345678 or $hash->{helper}{daysRegExpMessage}");
  254. }
  255. my %hdays=();
  256. @hdays{@tageGlobal} = undef;
  257. @hdays{@tage} = undef;
  258. @tage = sort keys %hdays;
  259. #Log3 $hash, 3, "Tage: " . Dumper \@tage;
  260. return (\@tage,$time,$para);
  261. }
  262. ################################################################################
  263. sub WeekdayTimer_daylistAsArray($$){
  264. my ($hash, $daylist) = @_;
  265. my $name = $hash->{NAME};
  266. my @days;
  267. my %hdays=();
  268. $daylist = lc($daylist);
  269. # Angaben der Tage verarbeiten
  270. # Aufzaehlung 1234 ...
  271. if ( $daylist =~ m/^[0-8]{0,9}$/g) {
  272. Log3 ($hash, 3, "[$name] " . '"7" in daylist now means $we(weekend) - see dokumentation!!!' )
  273. if (index($daylist, '7') != -1);
  274. @days = split("", $daylist);
  275. @hdays{@days} = undef;
  276. # Aufzaehlung Sa,So,... | Mo-Di,Do,Fr-Mo
  277. } elsif ($daylist =~ m/^($hash->{helper}{daysRegExp}(,|-|$)){0,7}$/g ) {
  278. my @subDays;
  279. my @aufzaehlungen = split (",", $daylist);
  280. foreach my $einzelAufzaehlung (@aufzaehlungen) {
  281. my @days = split ("-", $einzelAufzaehlung);
  282. my $days = @days;
  283. if ($days == 1) {
  284. #einzelner Tag: Sa
  285. $hdays{$hash->{dayNumber}{$days[0]}} = undef;
  286. } else {
  287. # von bis Angabe: Mo-Di
  288. my $von = $hash->{dayNumber}{$days[0]};
  289. my $bis = $hash->{dayNumber}{$days[1]};
  290. if ($von <= $bis) {
  291. @subDays = ($von .. $bis);
  292. } else {
  293. #@subDays = ($dayNumber{so} .. $bis, $von .. $dayNumber{sa});
  294. @subDays = ( 00 .. $bis, $von .. 06);
  295. }
  296. @hdays{@subDays}=undef;
  297. }
  298. }
  299. } else{
  300. %hdays = ();
  301. }
  302. my @tage = sort keys %hdays;
  303. return \@tage;
  304. }
  305. ################################################################################
  306. sub WeekdayTimer_EchteZeit($$$) {
  307. my ($hash, $d, $time) = @_;
  308. my $name = $hash->{NAME};
  309. my $now = time();
  310. my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($now);
  311. my $listOfDays = "";
  312. # Zeitangabe verarbeiten.
  313. $time = '"' . "$time" . '"' if($time !~ m/^\{.*\}$/g);
  314. my $date = $now+($d-$wday)*86400;
  315. my $timeString = '{ my $date='."$date;" .$time."}";
  316. my $eTimeString = eval( $timeString ); # must deliver HH:MM[:SS]
  317. if ($@) {
  318. $@ =~ s/\n/ /g;
  319. Log3 ($hash, 3, "[$name] " . $@ . ">>>$timeString<<<");
  320. $eTimeString = "00:00:00";
  321. }
  322. if ($eTimeString =~ m/^[0-2][0-9]:[0-5][0-9]$/g) { # HH:MM
  323. $eTimeString .= ":00"; # HH:MM:SS erzeugen
  324. } elsif ($eTimeString =~ m/^[0-2][0-9](:[0-5][0-9]){2,2}$/g) { # HH:MM:SS
  325. ; # ok.
  326. } else {
  327. Log3 ($hash, 1, "[$name] invalid time <$eTimeString> HH:MM[:SS]");
  328. $eTimeString = "00:00:00";
  329. }
  330. return $eTimeString;
  331. }
  332. ################################################################################
  333. sub WeekdayTimer_zeitErmitteln ($$$$$) {
  334. my ($now, $hour, $min, $sec, $days) = @_;
  335. my @jetzt_arr = localtime($now);
  336. #Stunden Minuten Sekunden
  337. $jetzt_arr[2] = $hour; $jetzt_arr[1] = $min; $jetzt_arr[0] = $sec;
  338. $jetzt_arr[3] += $days;
  339. my $next = timelocal_nocheck(@jetzt_arr);
  340. return $next;
  341. }
  342. ################################################################################
  343. sub WeekdayTimer_gatherSwitchingTimes {
  344. my $hash = shift;
  345. my $a = shift;
  346. my $name = $hash->{NAME};
  347. my @switchingtimes = ();
  348. my $conditionOrCommand;
  349. # switchingtime einsammeln
  350. while (@$a > 0) {
  351. #pruefen auf Angabe eines Schaltpunktes
  352. my $element = "";
  353. my @restoreElements = ();
  354. E: while (@$a > 0) {
  355. my $actualElement = shift @$a;
  356. push @restoreElements, $actualElement;
  357. $element = $element . $actualElement . " ";
  358. Log3 $hash, 5, "[$name] $element - trying to accept as a switchtime";
  359. # prüfen ob Anführungszeichen paarig sind
  360. my @quotes = ('"', "'" );
  361. foreach my $quote (@quotes){
  362. my $balancedSign = eval "((\$element =~ tr/$quote//))";
  363. if ($balancedSign % 2) { # ungerade Anzahl quotes, dann verlängern
  364. Log3 $hash, 5, "[$name] $element - unbalanced quotes: $balancedSign $quote found";
  365. next E;
  366. }
  367. }
  368. # prüfen ob öffnende/schliessende Klammern paarig sind
  369. my %signs = ('('=>')', '{'=>'}');
  370. foreach my $signOpened (keys(%signs)) {
  371. my $signClosed = $signs{$signOpened};
  372. my $balancedSign = eval "((\$element =~ tr/$signOpened//) - (\$element =~ tr/$signClosed//))";
  373. if ($balancedSign) { # öffnende/schließende Klammern nicht gleich, dann verlängern
  374. Log3 $hash, 5, "[$name] $element - unbalanced brackets $signOpened$signClosed:$balancedSign";
  375. next E;
  376. }
  377. }
  378. last;
  379. }
  380. # ein space am Ende wieder abschneiden
  381. $element = substr ($element, 0, length($element)-1);
  382. my @t = split(/\|/, $element);
  383. my $anzahl = @t;
  384. if ( ($anzahl == 2 || $anzahl == 3) && $t[0] gt "" && $t[1] gt "" ) {
  385. Log3 $hash, 4, "[$name] $element - accepted";
  386. push(@switchingtimes, $element);
  387. } else {
  388. Log3 $hash, 4, "[$name] $element - NOT accepted, must be command or condition";
  389. unshift @$a, @restoreElements;
  390. last;
  391. }
  392. }
  393. return (@switchingtimes);
  394. }
  395. ################################################################################
  396. sub WeekdayTimer_Language {
  397. my ($hash, $a) = @_;
  398. my $name = $hash->{NAME};
  399. # ggf. language optional Parameter
  400. my $langRegExp = "(" . join ("|", keys(%{$hash->{shortDays}})) . ")";
  401. my $language = shift @$a;
  402. if ($language =~ m/^$langRegExp$/g) {
  403. } else {
  404. Log3 ($hash, 3, "[$name] language: $language not recognized, use one of $langRegExp") if (length($language) == 2);
  405. unshift @$a, $language;
  406. $language = "de";
  407. }
  408. $hash->{LANGUAGE} = $language;
  409. $language = $hash->{LANGUAGE};
  410. return ($langRegExp, $language);
  411. }
  412. ################################################################################
  413. sub WeekdayTimer_GlobalDaylistSpec {
  414. my ($hash, $a) = @_;
  415. my $daylist = shift @$a;
  416. my @tage = @{WeekdayTimer_daylistAsArray($hash, $daylist)};
  417. my $tage = @tage;
  418. if ($tage > 0) {
  419. ;
  420. } else {
  421. unshift (@$a,$daylist);
  422. $daylist = "";
  423. }
  424. $hash->{GlobalDaylistSpec} = $daylist;
  425. }
  426. ################################################################################
  427. sub WeekdayTimer_SetTimerForMidnightUpdate($) {
  428. my ($myHash) = @_;
  429. my $hash = myGetHashIndirekt($myHash, (caller(0))[3]);
  430. return if (!defined($hash));
  431. my $now = time();
  432. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
  433. my $midnightPlus5Seconds = WeekdayTimer_zeitErmitteln ($now, 0, 0, 5, 1);
  434. #Log3 $hash, 3, "midnightPlus5Seconds------------>".FmtDateTime($midnightPlus5Seconds);
  435. myRemoveInternalTimer("SetTimerOfDay", $hash);
  436. my $newMyHash = myInternalTimer ("SetTimerOfDay", $midnightPlus5Seconds, "$hash->{TYPE}_SetTimerOfDay", $hash, 0);
  437. $newMyHash->{SETTIMERATMIDNIGHT} = 1;
  438. }
  439. ################################################################################
  440. sub WeekdayTimer_SetTimerOfDay($) {
  441. my ($myHash) = @_;
  442. my $hash = myGetHashIndirekt($myHash, (caller(0))[3]);
  443. return if (!defined($hash));
  444. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time());
  445. my $secSinceMidnight = 3600*$hour + 60*$min + $sec;
  446. $hash->{SETTIMERATMIDNIGHT} = $myHash->{SETTIMERATMIDNIGHT};
  447. WeekdayTimer_DeleteTimer($hash);
  448. WeekdayTimer_Profile ($hash);
  449. WeekdayTimer_SetTimer ($hash);
  450. delete $hash->{SETTIMERATMIDNIGHT};
  451. WeekdayTimer_SetTimerForMidnightUpdate( { HASH => $hash} );
  452. }
  453. ################################################################################
  454. sub WeekdayTimer_SetTimer($) {
  455. my $hash = shift;
  456. my $name = $hash->{NAME};
  457. my $now = time();
  458. my $isHeating = WeekdayTimer_isHeizung($hash);
  459. my $swip = AttrVal($name, "switchInThePast", 0);
  460. my $switchInThePast = ($swip || $isHeating);
  461. Log3 $hash, 4, "[$name] Heating recognized - switch in the past activated" if ($isHeating);
  462. 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});
  463. my @switches = sort keys %{$hash->{profil}};
  464. if ($#switches < 0) {
  465. Log3 $hash, 3, "[$name] no switches to send, due to possible errors.";
  466. return;
  467. }
  468. readingsSingleUpdate ($hash, "state", "inactive", 1) if (!defined $hash->{SETTIMERATMIDNIGHT});
  469. for(my $i=0; $i<=$#switches; $i++) {
  470. my $idx = $switches[$i];
  471. my $time = $hash->{profil}{$idx}{TIME};
  472. my $timToSwitch = $hash->{profil}{$idx}{EPOCH};
  473. my $tage = $hash->{profil}{$idx}{TAGE};
  474. my $para = $hash->{profil}{$idx}{PARA};
  475. my $secondsToSwitch = $timToSwitch - $now;
  476. my $isActiveTimer = WeekdayTimer_isAnActiveTimer ($hash, $tage, $para);
  477. readingsSingleUpdate ($hash, "state", "active", 1)
  478. if (!defined $hash->{SETTIMERATMIDNIGHT} && $isActiveTimer);
  479. if ($secondsToSwitch>-5 || defined $hash->{SETTIMERATMIDNIGHT} ) {
  480. if($isActiveTimer) {
  481. Log3 $hash, 4, "[$name] setTimer - timer seems to be active today: ".join("",@$tage)."|$time|$para";
  482. } else {
  483. Log3 $hash, 4, "[$name] setTimer - timer seems to be NOT active today: ".join("",@$tage)."|$time|$para ". $hash->{CONDITION};
  484. }
  485. myRemoveInternalTimer("$idx", $hash);
  486. myInternalTimer ("$idx", $timToSwitch, "$hash->{TYPE}_Update", $hash, 0);
  487. }
  488. }
  489. if (defined $hash->{SETTIMERATMIDNIGHT}) {
  490. return;
  491. }
  492. my ($aktIdx,$aktTime,$aktParameter,$nextTime,$nextParameter) =
  493. WeekdayTimer_searchAktNext($hash, time()+5);
  494. if(!defined $aktTime) {
  495. Log3 $hash, 3, "[$name] can not compute past switching time";
  496. }
  497. readingsSingleUpdate ($hash, "nextUpdate", FmtDateTime($nextTime), 1);
  498. readingsSingleUpdate ($hash, "nextValue", $nextParameter, 1);
  499. readingsSingleUpdate ($hash, "currValue", $aktParameter, 1); # HB
  500. if ($switchInThePast && defined $aktTime) {
  501. # Fensterkontakte abfragen - wenn einer im Status closed, dann Schaltung um 60 Sekunden verzögern
  502. if (WeekdayTimer_FensterOffen($hash, $aktParameter, $aktIdx)) {
  503. return;
  504. }
  505. # alle in der Vergangenheit liegenden Schaltungen sammeln und
  506. # nach 5 Sekunden in der Reihenfolge der Schaltzeiten
  507. # durch WeekdayTimer_delayedTimerInPast() als Timer einstellen
  508. # die Parameter merken wir uns kurzzeitig im hash
  509. # modules{WeekdayTimer}{timerInThePast}
  510. my $device = $hash->{DEVICE};
  511. Log3 $hash, 4, "[$name] past timer on $hash->{DEVICE} at ". FmtDateTime($aktTime). " with $aktParameter activated";
  512. my $parameter = $modules{WeekdayTimer}{timerInThePast}{$device}{$aktTime};
  513. $parameter = [] if (!defined $parameter);
  514. push (@$parameter,["$aktIdx", $aktTime, "$hash->{TYPE}_Update", $hash, 0]);
  515. $modules{WeekdayTimer}{timerInThePast}{$device}{$aktTime} = $parameter;
  516. my $tipHash = $modules{WeekdayTimer}{timerInThePastHash};
  517. $tipHash = $hash if (!defined $tipHash);
  518. $modules{WeekdayTimer}{timerInThePastHash} = $tipHash;
  519. myRemoveInternalTimer("delayed", $tipHash);
  520. myInternalTimer ("delayed", time()+5, "WeekdayTimer_delayedTimerInPast", $tipHash, 0);
  521. }
  522. }
  523. ################################################################################
  524. sub WeekdayTimer_delayedTimerInPast($) {
  525. my ($myHash) = @_;
  526. my $hash = myGetHashIndirekt($myHash, (caller(0))[3]);
  527. return if (!defined($hash));
  528. my $tim = time();
  529. my $tipIpHash = $modules{WeekdayTimer}{timerInThePast};
  530. foreach my $device ( keys %$tipIpHash ) {
  531. foreach my $time ( sort keys %{$tipIpHash->{$device}} ) {
  532. Log3 $hash, 4, "$device ".FmtDateTime($time)." ".($tim-$time)."s ";
  533. foreach my $para ( @{$tipIpHash->{$device}{$time}} ) {
  534. myRemoveInternalTimer(@$para[0], @$para[3]);
  535. my $mHash =myInternalTimer (@$para[0],@$para[1],@$para[2],@$para[3],@$para[4]);
  536. $mHash->{immerSchalten} = 1;
  537. }
  538. }
  539. }
  540. delete $modules{WeekdayTimer}{timerInThePast};
  541. delete $modules{WeekdayTimer}{timerInThePastHash}
  542. }
  543. ################################################################################
  544. sub WeekdayTimer_searchAktNext($$) {
  545. my ($hash, $now) = @_;
  546. my $name = $hash->{NAME};
  547. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
  548. #Log3 $hash, 3, "[$name] such--->".FmtDateTime($now);
  549. my ($oldTag, $oldTime, $oldPara , $oldIdx);
  550. my ($nextTag, $nextTime, $nextPara, $nextIdx);
  551. my $language = $hash->{LANGUAGE};
  552. my %shortDays = %{$hash->{shortDays}};
  553. my @realativeWdays = ($wday..6,0..$wday-1,$wday..6,0..6);
  554. for (my $i=0;$i<=$#realativeWdays;$i++) {
  555. my $relativeDay = $i-7;
  556. my $relWday = $realativeWdays[$i];
  557. foreach my $time (sort keys %{$hash->{helper}{SWITCHINGTIME}{$relWday}}) {
  558. my ($stunde, $minute, $sekunde) = split (":",$time);
  559. $oldTime = $nextTime;
  560. $oldPara = $nextPara;
  561. $oldIdx = $nextIdx;
  562. $oldTag = $nextTag;
  563. $nextTime = WeekdayTimer_zeitErmitteln ($now, $stunde, $minute, $sekunde, $relativeDay);
  564. $nextPara = $hash->{helper}{SWITCHINGTIME}{$relWday}{$time};
  565. $nextIdx = $hash->{profile_IDX}{$relWday}{$time};
  566. $nextTag = $relWday;
  567. #Log3 $hash, 3, $shortDays{$language}[$nextTag]." ".FmtDateTime($nextTime)." ".$nextPara." ".$nextIdx;
  568. if ($nextTime >= $now) {
  569. #Log3 $hash, 3, "oldIdx------------->$oldIdx";
  570. #Log3 $hash, 3, "oldTime------------>".FmtDateTime($oldTime);
  571. #Log3 $hash, 3, "oldPara------------>$oldPara";
  572. return ($oldIdx, $oldTime, $oldPara, $nextTime, $nextPara);
  573. }
  574. }
  575. }
  576. return (undef,undef,undef,undef);
  577. }
  578. ################################################################################
  579. sub WeekdayTimer_DeleteTimer($) {
  580. my $hash = shift;
  581. map {myRemoveInternalTimer ($_, $hash)} keys %{$hash->{profil}};
  582. }
  583. ################################################################################
  584. sub WeekdayTimer_Update($) {
  585. my ($myHash) = @_;
  586. my $hash = myGetHashIndirekt($myHash, (caller(0))[3]);
  587. return if (!defined($hash));
  588. my $name = $hash->{NAME};
  589. my $idx = $myHash->{MODIFIER};
  590. my $now = time();
  591. #my $sollZeit = $myHash->{TIME};
  592. #my $setModifier = WeekdayTimer_isHeizung($hash);
  593. #my $isHeating = $setModifier gt "";
  594. # Schaltparameter ermitteln
  595. my $tage = $hash->{profil}{$idx}{TAGE};
  596. my $time = $hash->{profil}{$idx}{TIME};
  597. #my $newParam = WeekdayTimer_evalAndcleanupParam($hash,$time,$hash->{profil}{$idx}{PARA}, $isHeating );
  598. my $newParam = $hash->{profil}{$idx}{PARA};
  599. #Log3 $hash, 3, "[$name] $idx ". $time . " " . $newParam . " " . join("",@$tage);
  600. # Fenserkontakte abfragen - wenn einer im Status closed, dann Schaltung um 60 Sekunden verzögern
  601. if (WeekdayTimer_FensterOffen($hash, $newParam, $idx)) {
  602. readingsSingleUpdate ($hash, "state", "open window", 1);
  603. return;
  604. }
  605. my $dieGanzeWoche = [7,8];
  606. my ($activeTimer, $activeTimerState);
  607. if (defined $myHash->{immerSchalten}) {
  608. $activeTimer = WeekdayTimer_isAnActiveTimer ($hash, $dieGanzeWoche, $newParam);
  609. $activeTimerState = WeekdayTimer_isAnActiveTimer ($hash, $tage, $newParam);
  610. Log3 $hash, 4, "[$name] Update - past timer activated";
  611. } else {
  612. $activeTimer = WeekdayTimer_isAnActiveTimer ($hash, $tage, $newParam);
  613. $activeTimerState = $activeTimer;
  614. Log3 $hash, 4, "[$name] Update - timer seems to be active today: ".join("",@$tage)."|$time|$newParam" if($activeTimer);
  615. }
  616. #Log3 $hash, 3, "activeTimer------------>$activeTimer";
  617. #Log3 $hash, 3, "activeTimerState------->$activeTimerState";
  618. my ($aktIdx, $aktTime, $aktParameter, $nextTime, $nextParameter) =
  619. WeekdayTimer_searchAktNext($hash, time()+5);
  620. my $device = $hash->{DEVICE};
  621. my $disabled = AttrVal($hash->{NAME}, "disable", 0);
  622. # ggf. Device schalten
  623. WeekdayTimer_Device_Schalten($hash, $newParam, $tage) if($activeTimer);
  624. readingsBeginUpdate($hash);
  625. readingsBulkUpdate ($hash, "nextUpdate", FmtDateTime($nextTime));
  626. readingsBulkUpdate ($hash, "nextValue", $nextParameter);
  627. readingsBulkUpdate ($hash, "currValue", $aktParameter); # HB
  628. readingsBulkUpdate ($hash, "state", $newParam ) if($activeTimerState);
  629. readingsEndUpdate ($hash, defined($hash->{LOCAL} ? 0 : 1));
  630. return 1;
  631. }
  632. ################################################################################
  633. sub WeekdayTimer_isAnActiveTimer ($$$) {
  634. my ($hash, $tage, $newParam) = @_;
  635. my $name = $hash->{NAME};
  636. my %specials = ( "%NAME" => $hash->{DEVICE}, "%EVENT" => $newParam);
  637. my $condition = WeekdayTimer_Condition ($hash, $tage);
  638. my $tageAsHash = WeekdayTimer_tageAsHash($hash, $tage);
  639. my $xPression = "{".$tageAsHash.";;".$condition ."}";
  640. $xPression = EvalSpecials($xPression, %specials);
  641. Log3 $hash, 5, "[$name] condition: $xPression";
  642. my $ret = AnalyzeCommandChain(undef, $xPression);
  643. #Log3 $hash, 3, "[$name] condition:>>>$ret<<< $xPression";
  644. Log3 $hash, 5, "[$name] result of condition:$ret";
  645. return $ret;
  646. }
  647. ################################################################################
  648. # {WeekdayTimer_isHeizung($defs{HeizungKueche_an_wt})}
  649. sub WeekdayTimer_isHeizung($) {
  650. my ($hash) = @_;
  651. my $name = $hash->{NAME};
  652. my $dHash = $defs{$hash->{DEVICE}};
  653. return "" if (!defined $dHash); # vorzeitiges Ende wenn das device nicht existiert
  654. my $dType = $dHash->{TYPE};
  655. return "" if (!defined($dType) || $dType eq "dummy" );
  656. my $dName = $dHash->{NAME};
  657. my @tempSet = ("desired-temp", "desiredTemperature", "desired", "thermostatSetpointSet");
  658. my $allSets = getAllSets($dName);
  659. foreach my $ts (@tempSet) {
  660. if ($allSets =~ m/$ts/) {
  661. Log3 $hash, 4, "[$name] device type heating recognized, setModifier:$ts";
  662. return $ts
  663. }
  664. }
  665. }
  666. ################################################################################
  667. #
  668. sub WeekdayTimer_FensterOffen ($$$) {
  669. my ($hash, $event, $time) = @_;
  670. my $name = $hash->{NAME};
  671. my %specials = (
  672. '%HEATING_CONTROL' => $hash->{NAME},
  673. '%WEEKDAYTIMER' => $hash->{NAME},
  674. '%NAME' => $hash->{DEVICE},
  675. '%EVENT' => $event,
  676. '%TIME' => $hash->{profil}{$time}{TIME},
  677. '$HEATING_CONTROL' => $hash->{NAME},
  678. '$WEEKDAYTIMER' => $hash->{NAME},
  679. '$NAME' => $hash->{DEVICE},
  680. '$EVENT' => $event,
  681. '$TIME' => $hash->{profil}{$time}{TIME},
  682. );
  683. my $verzoegerteAusfuehrungCond = AttrVal($hash->{NAME}, "delayedExecutionCond", "0");
  684. #$verzoegerteAusfuehrungCond = 'xxx(%WEEKDAYTIMER,%NAME,%HEATING_CONTROL,$WEEKDAYTIMER,$EVENT,$NAME,$HEATING_CONTROL)';
  685. my $nextRetry = time()+55+int(rand(10));
  686. my $epoch = $hash->{profil}{$time}{EPOCH};
  687. my $delay = int(time()) - $epoch;
  688. my $nextDelay = int($delay/60.+1.5)*60; # round to multiple of 60sec
  689. $nextRetry = $epoch + $nextDelay;
  690. Log3 $hash, 4, "[$name] time=".$hash->{profil}{$time}{TIME}."/$epoch delay=$delay, nextDelay=$nextDelay, nextRetry=$nextRetry";
  691. map { my $key = $_; $key =~ s/\$/\\\$/g;
  692. my $val = $specials{$_};
  693. $verzoegerteAusfuehrungCond =~ s/$key/$val/g
  694. } keys %specials;
  695. Log3 $hash, 4, "[$name] delayedExecutionCond:$verzoegerteAusfuehrungCond";
  696. my $verzoegerteAusfuehrung = eval($verzoegerteAusfuehrungCond);
  697. Log3 $hash, 4, "[$name] result of delayedExecutionCond:$verzoegerteAusfuehrung";
  698. if ($verzoegerteAusfuehrung) {
  699. if (!defined($hash->{VERZOEGRUNG})) {
  700. Log3 $hash, 3, "[$name] switch of $hash->{DEVICE} delayed - delayedExecutionCond: '$verzoegerteAusfuehrungCond' is TRUE";
  701. }
  702. if (defined($hash->{VERZOEGRUNG_IDX}) && $hash->{VERZOEGRUNG_IDX}!=$time) {
  703. Log3 $hash, 3, "[$name] timer at $hash->{profil}{$hash->{VERZOEGRUNG_IDX}}{TIME} skiped by new timer at $hash->{profil}{$time}{TIME}";
  704. myRemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
  705. }
  706. $hash->{VERZOEGRUNG_IDX} = $time;
  707. myRemoveInternalTimer("$time", $hash);
  708. myInternalTimer ("$time", $nextRetry, "$hash->{TYPE}_Update", $hash, 0);
  709. $hash->{VERZOEGRUNG} = 1;
  710. return 1;
  711. }
  712. my %contacts = ( "CUL_FHTTK" => { "READING" => "Window", "STATUS" => "(Open)", "MODEL" => "r" },
  713. "CUL_HM" => { "READING" => "state", "STATUS" => "(open|tilted)", "MODEL" => "r" },
  714. "EnOcean" => { "READING" => "state", "STATUS" => "(open)", "MODEL" => "r" },
  715. "ZWave" => { "READING" => "state", "STATUS" => "(open)", "MODEL" => "r" },
  716. "MAX" => { "READING" => "state", "STATUS" => "(open.*)", "MODEL" => "r" },
  717. "WeekdayTimer" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" },
  718. "Heating_Control" => { "READING" => "delayedExecution","STATUS" => "^1\$", "MODEL" => "a" }
  719. );
  720. my $fensterKontakte = AttrVal($hash->{NAME}, "windowSensor", "")." ".$hash->{NAME};
  721. $fensterKontakte =~ s/^\s+//;
  722. $fensterKontakte =~ s/\s+$//;
  723. Log3 $hash, 4, "[$name] list of window sensors found: '$fensterKontakte'";
  724. if ($fensterKontakte ne "" ) {
  725. my @kontakte = split("[ \t]+", $fensterKontakte);
  726. foreach my $fk (@kontakte) {
  727. if(!$defs{$fk}) {
  728. Log3 $hash, 3, "[$name] sensor <$fk> not found - check name.";
  729. } else {
  730. my $fk_hash = $defs{$fk};
  731. my $fk_typ = $fk_hash->{TYPE};
  732. if (!defined($contacts{$fk_typ})) {
  733. Log3 $hash, 3, "[$name] TYPE '$fk_typ' of $fk not yet supported, $fk ignored - inform maintainer";
  734. } else {
  735. my $reading = $contacts{$fk_typ}{READING};
  736. my $statusReg = $contacts{$fk_typ}{STATUS};
  737. my $model = $contacts{$fk_typ}{MODEL};
  738. my $windowStatus;
  739. if ($model eq "r") { ### Reading, sonst Attribut
  740. $windowStatus = ReadingsVal($fk,$reading,"nF");
  741. }else{
  742. $windowStatus = AttrVal ($fk,$reading,"nF");
  743. }
  744. if ($windowStatus eq "nF") {
  745. Log3 $hash, 3, "[$name] Reading/Attribute '$reading' of $fk not found, $fk ignored - inform maintainer" if ($model eq "r");
  746. } else {
  747. Log3 $hash, 5, "[$name] sensor '$fk' Reading/Attribute '$reading' is '$windowStatus'";
  748. if ($windowStatus =~ m/^$statusReg$/g) {
  749. if (!defined($hash->{VERZOEGRUNG})) {
  750. Log3 $hash, 3, "[$name] switch of $hash->{DEVICE} delayed - sensor '$fk' Reading/Attribute '$reading' is '$windowStatus'";
  751. }
  752. if (defined($hash->{VERZOEGRUNG_IDX}) && $hash->{VERZOEGRUNG_IDX}!=$time) {
  753. Log3 $hash, 3, "[$name] timer at $hash->{profil}{$hash->{VERZOEGRUNG_IDX}}{TIME} skiped by new timer at $hash->{profil}{$time}{TIME}";
  754. myRemoveInternalTimer($hash->{VERZOEGRUNG_IDX},$hash);
  755. }
  756. $hash->{VERZOEGRUNG_IDX} = $time;
  757. myRemoveInternalTimer("$time", $hash);
  758. myInternalTimer ("$time", $nextRetry, "$hash->{TYPE}_Update", $hash, 0);
  759. $hash->{VERZOEGRUNG} = 1;
  760. return 1
  761. }
  762. }
  763. }
  764. }
  765. }
  766. }
  767. if ($hash->{VERZOEGRUNG}) {
  768. Log3 $hash, 3, "[$name] delay of switching $hash->{DEVICE} stopped.";
  769. }
  770. delete $hash->{VERZOEGRUNG};
  771. delete $hash->{VERZOEGRUNG_IDX} if defined($hash->{VERZOEGRUNG_IDX});
  772. return 0;
  773. }
  774. ################################################################################
  775. sub WeekdayTimer_evalAndcleanupParam($$$$) {
  776. my ($hash,$time,$param,$isHeating) = @_;
  777. my $name = $hash->{DEVICE} ;
  778. my $wdName = $hash->{NAME};
  779. my $newParam = $param;
  780. if ($param =~ m/^{.*}$/) {
  781. Log3 $hash, 4, "[$wdName] calculating dynamic param before all: ....... $newParam";
  782. $newParam =~ s/\$NAME/$hash->{DEVICE}/g;
  783. $newParam =~ s/\$TIME/$time/g;
  784. Log3 $hash, 4, "[$wdName] calculating dynamic param after substitutions: $newParam";
  785. $newParam = eval $newParam;
  786. if ($@ || not defined $newParam) {
  787. Log3 $hash, 1, "[$wdName] problem calculating dynamic param: ........... $param";
  788. Log3 $hash, 1, "[$wdName] $@";
  789. } else {
  790. Log3 $hash, 4, "[$wdName] calculating dynamic param after eval: ........ $newParam";
  791. }
  792. }elsif($isHeating && $param =~ m/^\d{1,3}$/){
  793. $newParam = sprintf("%.1f", $param);
  794. }
  795. return $newParam;
  796. }
  797. ################################################################################
  798. sub WeekdayTimer_Device_Schalten($$$) {
  799. my ($hash, $newParam, $tage) = @_;
  800. my ($command, $condition, $tageAsHash) = "";
  801. my $name = $hash->{NAME}; ###
  802. my $dummy = "";
  803. my $now = time();
  804. #modifier des Zieldevices auswaehlen
  805. my $setModifier = WeekdayTimer_isHeizung($hash);
  806. $attr{$name}{commandTemplate} =
  807. 'set $NAME '. $setModifier .' $EVENT' if (!defined $attr{$name}{commandTemplate});
  808. $command = AttrVal($hash->{NAME}, "commandTemplate", "commandTemplate not found");
  809. $command = $hash->{COMMAND} if ($hash->{COMMAND} gt "");
  810. my $activeTimer = 1;
  811. my $isHeating = $setModifier gt "";
  812. my $aktParam = ReadingsVal($hash->{DEVICE}, $setModifier, "");
  813. $aktParam = sprintf("%.1f", $aktParam) if ($isHeating && $aktParam =~ m/^[0-9]{1,3}$/i);
  814. $newParam = sprintf("%.1f", $newParam) if ($isHeating && $newParam =~ m/^[0-9]{1,3}$/i);
  815. # my $aktParam = WeekdayTimer_evalAndcleanupParam($hash,$dummy,ReadingsVal($hash->{DEVICE}, $setModifier, ""),$isHeating);
  816. # newParam is already processed by evalAndcleanupParam()
  817. my $disabled = AttrVal($hash->{NAME}, "disable", 0);
  818. my $disabled_txt = $disabled ? " " : " not";
  819. Log3 $hash, 4, "[$name] aktParam:$aktParam newParam:$newParam - is $disabled_txt disabled";
  820. #Kommando ausführen
  821. if ($command && !$disabled && $activeTimer
  822. && $aktParam ne $newParam
  823. ) {
  824. $newParam =~ s/\\:/|/g;
  825. $newParam =~ s/:/ /g;
  826. $newParam =~ s/\|/:/g;
  827. my %specials = ( "%NAME" => $hash->{DEVICE}, "%EVENT" => $newParam);
  828. $command= EvalSpecials($command, %specials);
  829. Log3 $hash, 4, "[$name] command: '$command' executed with ".join(",", map { "$_=>$specials{$_}" } keys %specials);
  830. my $ret = AnalyzeCommandChain(undef, $command);
  831. Log3 ($hash, 3, $ret) if($ret);
  832. }
  833. }
  834. ################################################################################
  835. sub WeekdayTimer_tageAsHash($$) {
  836. my ($hash, $tage) = @_;
  837. my %days = map {$_ => 1} @$tage;
  838. map {delete $days{$_}} (7,8);
  839. return 'my $days={};map{$days->{$_}=1}'.'('.join (",", sort keys %days).')';
  840. }
  841. ################################################################################
  842. sub WeekdayTimer_Condition($$) {
  843. my ($hash, $tage) = @_;
  844. my $name = $hash->{NAME};
  845. Log3 $hash, 4, "[$name] condition:$hash->{CONDITION} - Tage:".join(",",@$tage);
  846. my $condition = "( ";
  847. $condition .= ($hash->{CONDITION} gt "") ? $hash->{CONDITION} : 1 ;
  848. $condition .= " && " . WeekdayTimer_TageAsCondition($tage);
  849. $condition .= ")";
  850. return $condition;
  851. }
  852. ################################################################################
  853. sub WeekdayTimer_TageAsCondition ($) {
  854. my $tage = shift;
  855. my %days = map {$_ => 1} @$tage;
  856. my $we = $days{7}; delete $days{7}; # $we
  857. my $notWe = $days{8}; delete $days{8}; #!$we
  858. my $tageExp = '(defined $days->{$wday}';
  859. $tageExp .= ' || $we' if defined $we;
  860. $tageExp .= ' || !$we' if defined $notWe;
  861. $tageExp .= ')';
  862. return $tageExp;
  863. }
  864. ################################################################################
  865. sub WeekdayTimer_Attr($$$$) {
  866. my ($cmd, $name, $attrName, $attrVal) = @_;
  867. $attrVal = 0 if(!defined $attrVal);
  868. my $hash = $defs{$name};
  869. if( $attrName eq "disable" ) {
  870. readingsSingleUpdate ($hash, "disabled", $attrVal, 1);
  871. } elsif ( $attrName eq "enable" ) {
  872. WeekdayTimer_SetTimerOfDay({ HASH => $hash});
  873. } elsif ( $attrName eq "switchInThePast" ) {
  874. $attr{$name}{$attrName} = $attrVal;
  875. WeekdayTimer_SetTimerOfDay({ HASH => $hash});
  876. }
  877. return undef;
  878. }
  879. ########################################################################
  880. sub WeekdayTimer_SetParm($) {
  881. my ($name) = @_;
  882. my $hash = $modules{WeekdayTimer}{defptr}{$name};
  883. if(defined $hash) {
  884. WeekdayTimer_DeleteTimer($hash);
  885. WeekdayTimer_SetTimer($hash);
  886. }
  887. }
  888. ################################################################################
  889. sub WeekdayTimer_SetAllParms() { # {WeekdayTimer_SetAllParms()}
  890. my @wdNamen = sort keys %{$modules{WeekdayTimer}{defptr}};
  891. foreach my $wdName ( @wdNamen ) {
  892. WeekdayTimer_SetParm($wdName);
  893. }
  894. Log3 undef, 3, "WeekdayTimer_SetAllParms() done on: ".join(" ",@wdNamen );
  895. }
  896. 1;
  897. =pod
  898. =item device
  899. =item summary sends parameter to devices at defined times
  900. =item summary_DE sendet Parameter an devices zu einer Liste mit festen Zeiten
  901. =begin html
  902. <a name="WeekdayTimer"></a>
  903. <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
  904. <h3>WeekdayTimer</h3>
  905. <ul>
  906. <br>
  907. <a name="weekdayTimer_define"></a>
  908. <b>Define</b>
  909. <ul>
  910. <code>define &lt;name&gt; WeekdayTimer &lt;device&gt; [&lt;language&gt;] [<u>weekdays</u>] &lt;profile&gt; &lt;command&gt;|&lt;condition&gt;</code>
  911. <br><br>
  912. to set a weekly profile for &lt;device&gt;<br><br>
  913. You can define different switchingtimes for every day.<br>
  914. The new parameter is sent to the &lt;device&gt; automatically with <br><br>
  915. <code>set &lt;device&gt; &lt;para&gt;</code><br><br>
  916. If you have defined a &lt;condition&gt; and this condition is false if the switchingtime has reached, no command will executed.<br>
  917. An other case is to define an own perl command with &lt;command&gt;.
  918. <p>
  919. The following parameter are defined:
  920. <ul><b>device</b><br>
  921. The device to switch at the given time.
  922. </ul>
  923. <p>
  924. <ul><b>language</b><br>
  925. Specifies the language used for definition and profiles.
  926. de,en,fr are possible. The parameter is optional.
  927. </ul>
  928. <p>
  929. <ul><b>weekdays</b><br>
  930. Specifies the days for all timer in the <b>WeekdayTimer</b>.
  931. The parameter is optional. For details see the weekdays part in profile.
  932. </ul>
  933. <p>
  934. <ul><b>profile</b><br>
  935. Define the weekly profile. All timings are separated by space. A switchingtime is defined
  936. by the following example: <br><br>
  937. <ul><b>[&lt;weekdays&gt;|]&lt;time&gt;|&lt;parameter&gt;</b></ul><br>
  938. <u>weekdays:</u> optional, if not set every day of the week is used.<br>
  939. Otherwise you can define a day with its number or its shortname.<br>
  940. <ul>
  941. <li>0,su sunday</li>
  942. <li>1,mo monday</li>
  943. <li>2,tu tuesday</li>
  944. <li>3,we wednesday</li>
  945. <li>4 ...</li>
  946. <li>7,$we weekend ($we)</li>
  947. <li>8,!$we weekday (!$we)</li>
  948. </ul><br>
  949. 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>
  950. <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>
  951. <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>
  952. </ul>
  953. <p>
  954. <ul><b>command</b><br>
  955. If no condition is set, all the rest is interpreted as a command. Perl-code is setting up
  956. by the well-known Block with {}.<br>
  957. Note: if a command is defined only this command is executed. In case of executing
  958. a "set desired-temp" command, you must define the hole commandpart explicitly by yourself.<br>
  959. <!----------------------------------------------------------------------------- -->
  960. <!-- -------------------------------------------------------------------------- -->
  961. The following parameter are replaced:<br>
  962. <ol>
  963. <li>$NAME => the device to switch</li>
  964. <li>$EVENT => the new temperature</li>
  965. </ol>
  966. </ul>
  967. <p>
  968. <ul><b>condition</b><br>
  969. if a condition is defined you must declare this with () and a valid perl-code.<br>
  970. The return value must be boolean.<br>
  971. The parameters $NAME and $EVENT will be interpreted.
  972. </ul>
  973. <p>
  974. <b>Examples:</b>
  975. <ul>
  976. <code>define shutter WeekdayTimer bath 12345|05:20|up 12345|20:30|down</code><br>
  977. Mo-Fr are setting the shutter at 05:20 to <b>up</b>, and at 20:30 <b>down</b>.<p>
  978. <code>define heatingBath WeekdayTimer bath 07:00|16 Mo,Tu,Th-Fr|16:00|18.5 20:00|eco
  979. {fhem("set dummy on"); fhem("set $NAME desired-temp $EVENT");}</code><br>
  980. At the given times and weekdays only(!) the command will be executed.<p>
  981. <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>
  982. The dimmer is only set to dimXX% if the dummy variable WeAreThere is "yes"(not a real live example).<p>
  983. If you want to have set all WeekdayTimer their current value (after a temperature lowering phase holidays)
  984. you can call the function <b>WeekdayTimer_SetParm("WD-device")</b> or <b>WeekdayTimer_SetAllParms()</b>.<br>
  985. This call can be automatically coupled to a dummy by a notify:<br>
  986. <code>define dummyNotify notify Dummy:. * {WeekdayTimer_SetAllTemps()}</code>
  987. <br><p>
  988. Some definitions without comment:
  989. <code>
  990. <pre>
  991. define wd Weekdaytimer device de 7|23:35|25 34|23:30|22 23:30|16 23:15|22 8|23:45|16
  992. define wd Weekdaytimer device de fr,$we|23:35|25 34|23:30|22 23:30|16 23:15|22 12|23:45|16
  993. define wd Weekdaytimer device de 20:35|25 34|14:30|22 21:30|16 21:15|22 12|23:00|16
  994. define wd Weekdaytimer device de mo-so, $we|{sunrise_abs_dat($date)}|on mo-so, $we|{sunset_abs_dat($date)}|off
  995. define wd Weekdaytimer device de mo-so,!$we|{sunrise_abs_dat($date)}|aus mo-so,!$we|{sunset_abs_dat($date)}|aus
  996. define wd Weekdaytimer device de {sunrise_abs_dat($date)}|19 {sunset_abs_dat($date)}|21
  997. define wd Weekdaytimer device de 22:35|25 23:00|16
  998. </code></pre>
  999. The daylist can be given globaly for the whole Weekdaytimer:<p>
  1000. <code><pre>
  1001. define wd Weekdaytimer device de !$we 09:00|19 (function("Ein"))
  1002. define wd Weekdaytimer device de $we 09:00|19 (function("Ein"))
  1003. define wd Weekdaytimer device de 78 09:00|19 (function("exit"))
  1004. define wd Weekdaytimer device de 57 09:00|19 (function("exit"))
  1005. define wd Weekdaytimer device de fr,$we 09:00|19 (function("exit"))
  1006. </code></pre>
  1007. </ul>
  1008. </ul>
  1009. <a name="WeekdayTimerset"></a>
  1010. <b>Set</b>
  1011. <code><b><font size="+1">set &lt;name&gt; &lt;value&gt;</font></b></code>
  1012. <br><br>
  1013. where <code>value</code> is one of:<br>
  1014. <pre>
  1015. <b>disable</b> # disables the Weekday_Timer
  1016. <b>enable</b> # enables the Weekday_Timer
  1017. </pre>
  1018. <b><font size="+1">Examples</font></b>:
  1019. <ul>
  1020. <code>set wd disable</code><br>
  1021. <code>set wd enable</code><br>
  1022. </ul>
  1023. </ul>
  1024. <a name="WeekdayTimerget"></a>
  1025. <b>Get</b> <ul>N/A</ul><br>
  1026. <a name="WeekdayTimerLogattr"></a>
  1027. <b>Attributes</b>
  1028. <ul>
  1029. <li>delayedExecutionCond <br>
  1030. 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.
  1031. <br><br>
  1032. <b>Example:</b>
  1033. <pre>
  1034. attr wd delayedExecutionCond isDelayed("$HEATING_CONTROL","$WEEKDAYTIMER","$TIME","$NAME","$EVENT")
  1035. </pre>
  1036. the parameter $WEEKDAYTIMER(timer name) $TIME $NAME(device name) $EVENT are replaced at runtime by the correct value.
  1037. <br><br>
  1038. <b>Example of a function:</b>
  1039. <pre>
  1040. sub isDelayed($$$$$) {
  1041. my($hc, $wdt, $tim, $nam, $event ) = @_;
  1042. my $theSunIsStillshining = ...
  1043. return ($tim eq "16:30" && $theSunIsStillshining) ;
  1044. }
  1045. </pre>
  1046. </li>
  1047. <li>switchInThePast<br>
  1048. 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.
  1049. Heatings are always switched in the past.
  1050. </li>
  1051. <li><a href="#disable">disable</a></li>
  1052. <li><a href="#loglevel">loglevel</a></li>
  1053. <li><a href="#event-on-update-reading">event-on-update-reading</a></li>
  1054. <li><a href="#event-on-change-reading">event-on-change-reading</a></li>
  1055. <li><a href="#stateFormat">stateFormat</a></li>
  1056. </ul><br>
  1057. =end html
  1058. =cut