98_WeekdayTimer.pm 48 KB

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