From e3116cbf2c6411505229afa6079ff43ebc0f6e52 Mon Sep 17 00:00:00 2001 From: HomeAuto_User <> Date: Sat, 21 Sep 2019 08:59:58 +0000 Subject: [PATCH] 88_Timer: new modul #103848 git-svn-id: https://svn.fhem.de/fhem/trunk@20216 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/88_Timer.pm | 1056 +++++++++++++++++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 1 + 3 files changed, 1058 insertions(+) create mode 100644 fhem/FHEM/88_Timer.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index 02e5eee97..305ec5914 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - new: 88_Timer: new modul - bugfix: 95_Dashboard: fix eliminating links to detail view - change: 95_Dashboard: change attribute "noLinks" to "dashboard_noLinks" - bugfix: 98_todoist: better API v8 fix diff --git a/fhem/FHEM/88_Timer.pm b/fhem/FHEM/88_Timer.pm new file mode 100644 index 000000000..f742285eb --- /dev/null +++ b/fhem/FHEM/88_Timer.pm @@ -0,0 +1,1056 @@ +################################################################# +# $Id$ +# +# The module is a timer for executing actions with only one InternalTimer. +# Github - FHEM Home Automation System +# https://github.com/fhem/Timer +# +# FHEM Forum: Automatisierung +# https://forum.fhem.de/index.php/board,20.0.html | https://forum.fhem.de/index.php/topic,103848.msg976039.html#new +# +# 2019 - HomeAuto_User & elektron-bbs +################################################################# +# notes: +# - module mit package umsetzen +################################################################# + + +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +my @action = ("on","off","Def"); +my @names; +my @designations; +my $description_all; +my $cnt_attr_userattr = 0; + +if (!$attr{global}{language} || $attr{global}{language} eq "EN") { + @designations = ("sunrise","sunset","local time","","SR","SS"); + $description_all = "all"; # using in RegEx + @names = ("No.","Year","Month","Day","Hour","Minute","Second","Device or label","Action","Mon","Tue","Wed","Thu","Fri","Sat","Sun","active",""); +} + +if ($attr{global}{language} && $attr{global}{language} eq "DE") { + @designations = ("Sonnenaufgang","Sonnenuntergang","lokale Zeit","Uhr","SA","SU"); + $description_all = "alle"; # using in RegEx + @names = ("Nr.","Jahr","Monat","Tag","Stunde","Minute","Sekunde","Gerät oder Bezeichnung","Aktion","Mo","Di","Mi","Do","Fr","Sa","So","aktiv",""); +} + +########################## +sub Timer_Initialize($) { + my ($hash) = @_; + + $hash->{AttrFn} = "Timer_Attr"; + $hash->{AttrList} = "disable:0,1 ". + "Show_DeviceInfo:alias,comment ". + "Timer_preselection:on,off ". + "Table_Border_Cell:on,off ". + "Table_Border:on,off ". + "Table_Header_with_time:on,off ". + "Table_Style:on,off ". + "Table_Size_TextBox:15,20,25,30,35,40,45,50 ". + "Table_View_in_room:on,off ". + "stateFormat:textField-long "; + $hash->{DefFn} = "Timer_Define"; + $hash->{SetFn} = "Timer_Set"; + $hash->{GetFn} = "Timer_Get"; + $hash->{UndefFn} = "Timer_Undef"; + $hash->{NotifyFn} = "Timer_Notify"; + #$hash->{FW_summaryFn} = "Timer_FW_Detail"; # displays html instead of status icon in fhemweb room-view + + $hash->{FW_detailFn} = "Timer_FW_Detail"; + $hash->{FW_deviceOverview} = 1; + $hash->{FW_addDetailToSummary} = 1; # displays html in fhemweb room-view +} + +########################## +# Predeclare Variables from other modules may be loaded later from fhem +our $FW_wname; + +########################## +sub Timer_Define($$) { + my ($hash, $def) = @_; + my @arg = split("[ \t][ \t]*", $def); + my $name = $arg[0]; ## Definitionsname, mit dem das Gerät angelegt wurde + my $typ = $hash->{TYPE}; ## Modulname, mit welchem die Definition angelegt wurde + my $filelogName = "FileLog_$name"; + my ($cmd, $ret); + my ($autocreateFilelog, $autocreateHash, $autocreateName, $autocreateDeviceRoom, $autocreateWeblinkRoom) = ('%L' . $name . '-%Y-%m.log', undef, 'autocreate', $typ, $typ); + $hash->{NOTIFYDEV} = "global,TYPE=$typ"; + + return "Usage: define $name" if(@arg != 2); + + if ($init_done) { + if (!defined(AttrVal($autocreateName, "disable", undef)) && !exists($defs{$filelogName})) { + ### create FileLog ### + $autocreateFilelog = AttrVal($autocreateName, "filelog", undef) if (defined AttrVal($autocreateName, "filelog", undef)); + $autocreateFilelog =~ s/%NAME/$name/g; + $cmd = "$filelogName FileLog $autocreateFilelog $name"; + Log3 $filelogName, 2, "$name: define $cmd"; + $ret = CommandDefine(undef, $cmd); + if($ret) { + Log3 $filelogName, 2, "$name: ERROR: $ret"; + } else { + ### Attributes ### + CommandAttr($hash,"$filelogName room $autocreateDeviceRoom"); + CommandAttr($hash,"$filelogName logtype text"); + CommandAttr($hash,"$name room $autocreateDeviceRoom"); + } + } + + ### Attributes ### + CommandAttr($hash,"$name room $typ") if (!defined AttrVal($name, "room", undef)); # set room, if only undef --> new def + } + + ### default value´s ### + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "state" , "Defined"); + readingsBulkUpdate($hash, "internalTimer" , "stop"); + readingsEndUpdate($hash, 0); + return undef; +} + +##################### +sub Timer_Set($$$@) { + my ( $hash, $name, @a ) = @_; + return "no set value specified" if(int(@a) < 1); + + my $setList = "addTimer:noArg "; + my $cmd = $a[0]; + my $cmd2 = $a[1]; + my $Timers_Count = 0; + my $Timers_Count2; + my $Timers_diff = 0; + my $Timer_preselection = AttrVal($name,"Timer_preselection","off"); + my $value; + + foreach my $d (sort keys %{$hash->{READINGS}}) { + if ($d =~ /^Timer_(\d)+$/) { + $Timers_Count++; + $d =~ s/Timer_//; + $setList.= "deleteTimer:" if ($Timers_Count == 1); + $setList.= $d.","; + } + } + + if ($Timers_Count != 0) { + $setList = substr($setList, 0, -1); # cut last , + $setList.= " saveTimers:noArg"; + } + + $setList.= " sortTimer:noArg" if ($Timers_Count > 1); + + Log3 $name, 4, "$name: Set | cmd=$cmd" if ($cmd ne "?"); + + if ($cmd eq "sortTimer") { + my @timers_unsortet; + my $userattr_new = ""; + my @userattr_values; + my $timer_nr_new; + RemoveInternalTimer($hash, "Timer_Check"); + + foreach my $readingsName (keys %{$hash->{READINGS}}) { + if ($readingsName =~ /^Timer_(\d+)$/) { + my $value = ReadingsVal($name, $readingsName, 0); + $value =~ /^.*\d{2},(.*),(on|off|Def)/; + push(@timers_unsortet,$1.",".ReadingsVal($name, $readingsName, 0).",$readingsName"); # unsort Reading Wert in Array + readingsDelete($hash, $readingsName); # Timer loeschen + } + } + + my @timers_sort = sort @timers_unsortet; # Timer in neues Array sortieren + + for (my $i=0; $i 0) { # write userattr_values + for (my $i=0; $i{READINGS}}) { + if ($d =~ /^Timer_(\d+)$/) { + $Timers_Count++; + $Timers_Count2 = $1 * 1; + if ($Timers_Count != $Timers_Count2 && $Timers_diff == 0) { # only for diff + $Timers_diff++; + last; + } + } + } + + if ($Timer_preselection eq "on") { + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + $value = $year + 1900 .",".sprintf("%02s", ($mon + 1)).",".sprintf("%02s", $mday).",".sprintf("%02s", $hour).",".sprintf("%02s", $min).",00,,on,1,1,1,1,1,1,1,0"; + } else { + $value = "$description_all,$description_all,$description_all,$description_all,$description_all,00,,on,1,1,1,1,1,1,1,0"; + } + + $Timers_Count = $Timers_Count + 1 if ($Timers_diff == 0); + readingsSingleUpdate($hash, "Timer_".sprintf("%02s", $Timers_Count) , $value, 1); + } + + if ($cmd eq "saveTimers") { + open(SaveDoc, '>', "./FHEM/lib/$name"."_conf.txt") || return "ERROR: file $name"."_conf.txt can not open!"; + foreach my $d (sort keys %{$hash->{READINGS}}) { + print SaveDoc "Timer_".$1.",".$hash->{READINGS}->{$d}->{VAL}."\n" if ($d =~ /^Timer_(\d+)$/); + } + + print SaveDoc "\n"; + + foreach my $e (sort keys %{$attr{$name}}) { + my $LE = ""; + $LE = "\n" if (AttrVal($name, $e, undef) !~ /\n$/); + print SaveDoc $e.",".AttrVal($name, $e, undef).$LE if ($e =~ /^Timer_(\d+)_set$/); + } + close(SaveDoc); + } + + if ($cmd eq "deleteTimer") { + readingsDelete($hash, "Timer_".$cmd2); + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "state" , "Timer_$cmd2 deleted"); + + if ($Timers_Count == 0) { + readingsBulkUpdate($hash, "internalTimer" , "stop",1); + RemoveInternalTimer($hash, "Timer_Check"); + } + + readingsEndUpdate($hash, 1); + + my $deleteTimer = "Timer_$cmd2"."_set:textField-long"; + Timer_delFromUserattr($hash,$deleteTimer); + addStructChange("modify", $name, "attr $name userattr Timer_$cmd2"); # note with question mark + } + + return $setList if ( $a[0] eq "?"); + return "Unknown argument $cmd, choose one of $setList" if (not grep /$cmd/, $setList); + return undef; +} + +##################### +sub Timer_Get($$$@) { + my ( $hash, $name, $cmd, @a ) = @_; + my $list = "loadTimers:no,yes"; + my $cmd2 = $a[0]; + my $Timer_cnt_name = -1; + + if ($cmd eq "loadTimers") { + if ($cmd2 eq "no") { + return ""; + } + + if ($cmd2 eq "yes") { + my $error = 0; + my @lines_readings; + my @attr_values; + my @attr_values_names; + RemoveInternalTimer($hash, "Timer_Check"); + + open (InputFile,"<./FHEM/lib/$name"."_conf.txt") || return "ERROR: No file $name"."_conf.txt found in ./FHEM/lib directory from FHEM!"; + while (){ + if ($_ =~ /^Timer_\d{2},/) { + chomp ($_); # Zeilenende entfernen + push(@lines_readings,$_); # lines in array + my @values = split(",",$_); # split line in array to check + $error++ if (scalar(@values) != 17); + push(@attr_values_names, $values[0]."_set") if($values[8] eq "Def"); + for (my $i=0;$i<@values;$i++) { + $error++ if ($i == 0 && $values[0] !~ /^Timer_\d{2}$/); + $error++ if ($i == 1 && $values[1] !~ /^\d{4}$|^$description_all$/); + if ($i >= 2 && $i <= 5 && $values[$i] ne "$description_all") { + $error++ if ($i >= 2 && $i <= 3 && $values[$i] !~ /^\d{2}$/); + $error++ if ($i == 2 && ($values[2] * 1) < 1 && ($values[2] * 1) > 12); + $error++ if ($i == 3 && ($values[3] * 1) < 1 && ($values[3] * 1) > 31); + + if ($i >= 4 && $i <= 5 && $values[$i] ne $designations[4] && $values[$i] ne $designations[5]) { # SA -> 4 SU -> 5 + $error++ if ($i >= 4 && $i <= 5 && $values[$i] !~ /^\d{2}$/); + $error++ if ($i == 4 && ($values[4] * 1) > 23); + $error++ if ($i == 5 && ($values[5] * 1) > 59); + } + } + $error++ if ($i == 6 && $values[$i] % 10 != 0); + $error++ if ($i == 8 && not grep { $values[$i] eq $_ } @action); + $error++ if ($i >= 9 && $values[$i] ne "0" && $values[$i] ne "1"); + + if ($error != 0) { + close InputFile; + Timer_Check($hash); + return "ERROR: your file is NOT valid! ($error)"; + } + } + } + + if ($_ =~ /^Timer_\d{2}_set,/) { + $Timer_cnt_name++; + push(@attr_values, substr($_,13)); + } elsif ($_ !~ /^Timer_\d{2},/) { + $attr_values[$Timer_cnt_name].= $_ if ($Timer_cnt_name >= 0); + if ($_ =~ /.*}.*/){ # letzte } Klammer finden + my $err = perlSyntaxCheck($attr_values[$Timer_cnt_name], ()); # check PERL Code + if($err) { + $err = "ERROR: your file is NOT valid! \n \n".$err; + close InputFile; + Timer_Check($hash); + return $err; + } + } + } + } + close InputFile; + + foreach my $d (sort keys %{$hash->{READINGS}}) { # delete all readings + readingsDelete($hash, $d) if ($d =~ /^Timer_(\d+)$/); + } + + foreach my $f (sort keys %{$attr{$name}}) { # delete all attributes Timer_xx_set ... + CommandDeleteAttr($hash, $name." ".$f) if ($f =~ /^Timer_(\d+)_set$/); + } + + my @userattr_values = split(" ", AttrVal($name, "userattr", "none")); + for (my $i=0;$i<@userattr_values;$i++) { # delete userattr values Timer_xx_set:textField-long ... + delFromDevAttrList($name, $userattr_values[$i]) if ($userattr_values[$i] =~ /^Timer_(\d+)_set:textField-long$/); + } + + foreach my $e (@lines_readings) { # write new readings + my $Timer_nr = substr($e,0,8); + readingsSingleUpdate($hash, "$Timer_nr" , substr($e,9,length($e)-9), 1) if ($e =~ /^Timer_\d{2},/); + } + + for (my $i=0;$i<@attr_values_names;$i++) { # write new userattr + addToDevAttrList($name,$attr_values_names[$i].":textField-long"); + } + + for (my $i=0;$i<@attr_values;$i++) { # write new attr value + CommandAttr($hash,"$name $attr_values_names[$i] $attr_values[$i]"); + } + + readingsSingleUpdate($hash, "state" , "Timers loaded", 1); + FW_directNotify("FILTER=$name", "#FHEMWEB:WEB", "location.reload('true')", ""); + Timer_Check($hash); + + return undef; + } + } + + return "Unknown argument $cmd, choose one of $list"; +} + +##################### +sub Timer_Attr() { + my ($cmd, $name, $attrName, $attrValue) = @_; + my $hash = $defs{$name}; + my $typ = $hash->{TYPE}; + + if ($cmd eq "set" && $init_done == 1 ) { + Log3 $name, 3, "$name: Attr | set $attrName to $attrValue"; + if ($attrName eq "disable") { + if ($attrValue eq "1") { + readingsSingleUpdate($hash, "internalTimer" , "stop",1); + RemoveInternalTimer($hash, "Timer_Check"); + } elsif ($attrValue eq "0") { + Timer_Check($hash); + } + } + + if ($attrName =~ /^Timer_\d{2}_set$/) { + my $err = perlSyntaxCheck($attrValue, ()); # check PERL Code + return $err if($err); + } + } + + if ($cmd eq "del") { + Log3 $name, 3, "$name: Attr | Attributes $attrName deleted"; + if ($attrName eq "disable") { + Timer_Check($hash); + } + + if ($attrName eq "userattr") { + if (defined AttrVal($FW_wname, "confirmDelete", undef) && AttrVal($FW_wname, "confirmDelete", undef) == 0) { + $cnt_attr_userattr++; + return "Please execute again if you want to force the attribute to delete!" if ($cnt_attr_userattr == 1); + $cnt_attr_userattr = 0; + } + } + } +} + +##################### +sub Timer_Undef($$) { + my ($hash, $arg) = @_; + my $name = $hash->{NAME}; + + RemoveInternalTimer($hash, "Timer_Check"); + Log3 $name, 4, "$name: Undef | device is delete"; + + return undef; +} + +##################### +sub Timer_Notify($$) { + my ($hash, $dev_hash) = @_; + my $name = $hash->{NAME}; + my $typ = $hash->{TYPE}; + return "" if(IsDisabled($name)); # Return without any further action if the module is disabled + my $devName = $dev_hash->{NAME}; # Device that created the events + my $events = deviceEvents($dev_hash, 1); + + if($devName eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events}) && $typ eq "Timer") { + Log3 $name, 4, "$name: Notify is running and starting $name"; + Timer_Check($hash); + } + + return undef; +} + +##### HTML-Tabelle Timer-Liste erstellen ##### +sub Timer_FW_Detail($$$$) { + my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn. + my $hash = $defs{$d}; + my $name = $hash->{NAME}; + my $html = ""; + my $selected = ""; + my $Timers_Count = 0; + my $Table_Border = AttrVal($name,"Table_Border","off"); + my $Table_Border_Cell = AttrVal($name,"Table_Border_Cell","off"); + my $Table_Header_with_time = AttrVal($name,"Table_Header_with_time","off"); + my $Table_Size_TextBox = AttrVal($name,"Table_Size_TextBox",20); + my $Table_Style = AttrVal($name,"Table_Style","off"); + my $Table_View_in_room = AttrVal($name,"Table_View_in_room","on"); + my $style_background = ""; + my $style_code1 = ""; + my $style_code2 = ""; + my $time = FmtDateTime(time()); + my $FW_room_dupl = $FW_room; + my @timer_nr; + my $cnt_max = scalar(@names); + + return $html if((!AttrVal($name, "room", undef) && $FW_room ne "") || ($Table_View_in_room eq "off" && $FW_room ne "")); + + if ($Table_Style eq "on") { + ### style via CSS for Checkbox ### + $html.= ''; + } + + Log3 $name, 4, "$name: attr2html is running"; + + foreach my $d (sort keys %{$hash->{READINGS}}) { + if ($d =~ /^Timer_\d+$/) { + $Timers_Count++; + push(@timer_nr, substr($d,index($d,"_")+1)); + } + } + $style_code2 = "border:2px solid #00FF00;" if($Table_Border eq "on"); + + $html.= "
$designations[0]: ".sunrise_abs("REAL")." $designations[3]  |  $designations[1]: ".sunset_abs("REAL")." $designations[3]  |  $designations[2]: ".TimeNow()." $designations[3]
" if($Table_Header_with_time eq "on"); + $html.= "
"; + + # Timer Jahr Monat Tag Stunde Minute Sekunde Gerät Aktion Mo Di Mi Do Fr Sa So aktiv speichern + # ------------------------------------------------------------------------------------------------- + # 2019 09 03 18 15 00 Player on 0 0 0 0 0 0 0 0 + # Spalte: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + # ------------------------------------------------------------------------------------------------- + # T 1 id: 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ($id = timer_nr * 20 + $Spalte) + # T 2 id: 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 ($id = timer_nr * 20 + $Spalte) + + ## Überschriften + $html.= ""; + #### + $style_code1 = "border:1px solid #D8D8D8;" if($Table_Border_Cell eq "on"); + for(my $spalte = 0; $spalte <= $cnt_max - 1; $spalte++) { + $html.= "" if ($spalte >= 1 && $spalte <= 6); ## definierte Breite bei Auswahllisten + $html.= "" if ($spalte > 6 && $spalte < $cnt_max); ## auto Breite + $html.= "" if ($spalte == 0); ## auto Breite + $html.= "" if ($spalte == $cnt_max - 1); ## auto Breite + } + $html.= ""; + + for(my $zeile = 0; $zeile < $Timers_Count; $zeile++) { + $style_background = "background-color:#F0F0D8;" if ($zeile % 2 == 0); + $style_background = "" if ($zeile % 2 != 0); + $html.= ""; + my $id = $timer_nr[$zeile] * 20; # id 20, 40, 60 ... + # Log3 $name, 3, "$name: Zeile $zeile, id $id, Start"; + + my @select_Value = split(",", ReadingsVal($name, "Timer_".$timer_nr[$zeile], "$description_all,$description_all,$description_all,$description_all,$description_all,00,Lampe,on,0,0,0,0,0,0,0,0,,")); + for(my $spalte = 1; $spalte <= $cnt_max; $spalte++) { + $style_code1 .= "Padding-bottom:5px; " if ($zeile == $Timers_Count - 1); + $html.= "" if ($spalte == 1); # Spalte Timer-Nummer + if ($spalte >=2 && $spalte <= 7) { ## DropDown-Listen fuer Jahr, Monat, Tag, Stunde, Minute, Sekunde + my $start = 0; # Stunde, Minute, Sekunde + my $stop = 12; # Monat + my $step = 1; # Jahr, Monat, Tag, Stunde, Minute + $start = substr($time,0,4) if ($spalte == 2); # Jahr + $stop = $start + 10 if ($spalte == 2); # Jahr + $start = 1 if ($spalte == 3 || $spalte == 4); # Monat, Tag + $stop = 31 if ($spalte == 4); # Tag + $stop = 23 if ($spalte == 5); # Stunde + $stop = 59 if ($spalte == 6); # Minute + $stop = 50 if ($spalte == 7); # Sekunde + $step = 10 if ($spalte == 7); # Sekunde + $id++; + + # Log3 $name, 3, "$name: Zeile $zeile, id $id, select"; + $html.= ""; + } + + if ($spalte == 8) { ## Spalte Geraete + $id ++; + my $comment = ""; + $comment = AttrVal($select_Value[$spalte-2],"alias","") if (AttrVal($name,"Show_DeviceInfo","") eq "alias"); + $comment = AttrVal($select_Value[$spalte-2],"comment","") if (AttrVal($name,"Show_DeviceInfo","") eq "comment"); + $html.= ""; + } + + if ($spalte == 9) { ## DropDown-Liste Aktion + $id ++; + $html.= ""; + } + + ## Spalte Wochentage + aktiv + Log3 $name, 5, "$name: attr2html | Timer=".$timer_nr[$zeile]." ".$names[$spalte-1]."=".$select_Value[$spalte-2]." cnt_max=$cnt_max ($spalte)" if ($spalte > 1 && $spalte < $cnt_max); + + ## existierender Timer + if ($spalte > 9 && $spalte < $cnt_max) { + $id ++; + $html.= "" if ($select_Value[$spalte-2] eq "0"); + $html.= "" if ($select_Value[$spalte-2] eq "1"); + } + ## Button Speichern + if ($spalte == $cnt_max) { + $id ++; + $html.= ""; # 🖫 💾 + } + } + $html.= ""; ## Zeilenende + } + $html.= "
".$names[$spalte]."".$names[$spalte]."".$names[$spalte]."".$names[$spalte]."
".sprintf("%02s", $timer_nr[$zeile])."
$comment
"; ## Tabellenende + + ## Tabellenende + Script + $html.= '
+ + '; + + return $html; +} + +### for function from pushed_savebutton ### +sub FW_pushed_savebutton { + my $name = shift; + my $hash = $defs{$name}; + my $selected_buttons = shift; # neu,alle,alle,alle,alle,alle,00,Beispiel,on,0,0,0,0,0,0,0,0 + my @selected_buttons = split("," , $selected_buttons); + my $timer = $selected_buttons[0]; + my $timers_count = 0; # Timer by counting + my $timers_count2 = 0; # need to check 1 + 1 + my $timers_diff = 0; # need to check 1 + 1 + my $FW_room_dupl = shift; + my $cnt_names = scalar(@selected_buttons); + my $devicefound = 0; # to check device exists + + my $timestamp = TimeNow(); # Time now -> 2016-02-16 19:34:24 + my @timestamp_values = split(/-|\s|:/ , $timestamp); # Time now splitted + my ($sec, $min, $hour, $mday, $month, $year) = ($timestamp_values[5], $timestamp_values[4], $timestamp_values[3], $timestamp_values[2], $timestamp_values[1], $timestamp_values[0]); + + Log3 $name, 4, "$name: FW_pushed_savebutton is running"; + + foreach my $d (sort keys %{$hash->{READINGS}}) { + if ($d =~ /^Timer_(\d+)$/) { + $timers_count++; + $timers_count2 = $1 * 1; + if ($timers_count != $timers_count2 && $timers_diff == 0) { # only for diff + $timer = $timers_count; + $timers_diff = 1; + } + } + } + + for(my $i = 0;$i < $cnt_names;$i++) { + Log3 $name, 5, "$name: FW_pushed_savebutton | ".$names[$i]." -> ".$selected_buttons[$i]; + ## to set time to check input ## SA -> pos 4 array | SU -> pos 5 array ## + if ($i >= 1 && $i <=6 && ( $selected_buttons[$i] ne "$description_all" && $selected_buttons[$i] ne $designations[4] && $selected_buttons[$i] ne $designations[5] )) { + $sec = $selected_buttons[$i] if ($i == 6); + $min = $selected_buttons[$i] if ($i == 5); + $hour = $selected_buttons[$i] if ($i == 4); + $mday = $selected_buttons[$i] if ($i == 3); + $month = $selected_buttons[$i]-1 if ($i == 2); + $year = $selected_buttons[$i]-1900 if ($i == 1); + } + + if ($i == 7) { + Log3 $name, 5, "$name: FW_pushed_savebutton | check: exists device or name -> ".$selected_buttons[$i]; + + foreach my $d (sort keys %defs) { + if (defined($defs{$d}{NAME}) && $defs{$d}{NAME} eq $selected_buttons[$i]) { + $devicefound++; + Log3 $name, 5, "$name: FW_pushed_savebutton | ".$selected_buttons[$i]." is checked and exists"; + } + } + + if ($devicefound == 0 && ($selected_buttons[$i+1] eq "on" || $selected_buttons[$i+1] eq "off")) { + Log3 $name, 5, "$name: FW_pushed_savebutton | ".$selected_buttons[$i]." is NOT exists"; + return "ERROR: device not exists or no description! NO timer saved!"; + } + } + } + + return "ERROR: The time is in the past. Please set a time in the future!" if ((time() - fhemTimeLocal($sec, $min, $hour, $mday, $month, $year)) > 0); + return "ERROR: The next switching point is too small!" if ((fhemTimeLocal($sec, $min, $hour, $mday, $month, $year) - time()) < 60); + + readingsDelete($hash,"Timer_".sprintf("%02s", $timer)."_set") if ($selected_buttons[8] ne "Def" && ReadingsVal($name, "Timer_".sprintf("%02s", $timer)."_set", 0) ne "0"); + + my $oldValue = ReadingsVal($name,"Timer_".sprintf("%02s", $selected_buttons[0]) ,0); + my @Value_split = split(/,/ , $oldValue); + $oldValue = $Value_split[7]; + my $newValue = substr($selected_buttons,(index($selected_buttons,",") + 1)); + @Value_split = split(/,/ , $newValue); + $newValue = $Value_split[7]; + + if ($Value_split[6] eq "" && $Value_split[7] eq "Def") { # standard name, if no name set in Def option + my $replace = "Timer_".sprintf("%02s", $selected_buttons[0]); + $selected_buttons =~ s/,,/,$replace,/g; + } + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "Timer_".sprintf("%02s", $selected_buttons[0]) , substr($selected_buttons,(index($selected_buttons,",") + 1))); + + my $state = "Timer_".sprintf("%02s", $selected_buttons[0])." saved"; + my $userattrName = "Timer_".sprintf("%02s", $selected_buttons[0])."_set:textField-long"; + my $reload = 0; + + if (($oldValue eq "on" || $oldValue eq "off") && $newValue eq "Def") { + $state = "Timer_".sprintf("%02s", $selected_buttons[0])." is save and added to userattr"; + addToDevAttrList($name,$userattrName); + addStructChange("modify", $name, "attr $name userattr"); # note with question mark + $reload++; + } + + if ($oldValue eq "Def" && ($newValue eq "on" || $newValue eq "off")) { + $state = "Timer_".sprintf("%02s", $selected_buttons[0])." is save and deleted from userattr"; + Timer_delFromUserattr($hash,$userattrName) if ($attr{$name}{userattr}); + addStructChange("modify", $name, "attr $name userattr"); # note with question mark + $reload++; + } + + readingsBulkUpdate($hash, "state" , $state, 1); + readingsEndUpdate($hash, 1); + + FW_directNotify("FILTER=room=$FW_room_dupl", "#FHEMWEB:WEB", "location.reload('true')", "") if ($FW_room_dupl); + FW_directNotify("FILTER=$name", "#FHEMWEB:WEB", "location.reload('true')", "") if ($reload != 0); # need to view question mark + + Timer_Check($hash) if ($selected_buttons[16] eq "1" && ReadingsVal($name, "internalTimer", "stop") eq "stop"); + + return; +} + +### for delete Timer value from userattr ### +sub Timer_delFromUserattr($$) { + my $hash = shift; + my $deleteTimer = shift; + my $name = $hash->{NAME}; + + if ($attr{$name}{userattr} =~ /$deleteTimer/) { + delFromDevAttrList($name, $deleteTimer); + Log3 $name, 3, "$name: delete $deleteTimer from userattr Attributes"; + } +} + +### for Check ### +sub Timer_Check($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my @timestamp_values = split(/-|\s|:/ , TimeNow()); # Time now (2016-02-16 19:34:24) splitted in array + my $dayOfWeek = strftime('%w', localtime); # Wochentag + $dayOfWeek = 7 if ($dayOfWeek eq "0"); # Sonntag nach hinten (Position 14 im Array) + my $intervall = 60; # Intervall to start new InternalTimer (standard) + my $cnt_activ = 0; # counter for activ timers + my ($seconds, $microseconds) = gettimeofday(); + my @sunriseValues = split(":" , sunrise_abs("REAL")); # Sonnenaufgang (06:34:24) splitted in array + my @sunsetValues = split(":" , sunset_abs("REAL")); # Sonnenuntergang (19:34:24) splitted in array + my $state;; + + Log3 $name, 4, "$name: Check is running, Sonnenaufgang $sunriseValues[0]:$sunriseValues[1]:$sunriseValues[2], Sonnenuntergang $sunsetValues[0]:$sunsetValues[1]:$sunsetValues[2]"; + Log3 $name, 4, "$name: Check is running, drift $microseconds microSeconds"; + + foreach my $d (keys %{$hash->{READINGS}}) { + if ($d =~ /^Timer_\d+$/) { + my @values = split("," , $hash->{READINGS}->{$d}->{VAL}); + #Jahr Monat Tag Stunde Minute Sekunde Gerät Aktion Mo Di Mi Do Fr Sa So aktiv + #alle, alle, alle, alle, alle, 00, BlueRay_Player_LG, on, 0, 0, 0, 0, 0, 0, 0, 0 + #0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + my $set = 1; + if ($values[15] == 1) { # Timer aktiv + $cnt_activ++; + $values[3] = $sunriseValues[0] if $values[3] eq $designations[4]; # Stunde | Sonnenaufgang -> pos 4 array + $values[4] = $sunriseValues[1] if $values[4] eq $designations[4]; # Minute | Sonnenaufgang -> pos 4 array + $values[3] = $sunsetValues[0] if $values[3] eq $designations[5]; # Stunde | Sonnenuntergang -> pos 5 array + $values[4] = $sunsetValues[1] if $values[4] eq $designations[5]; # Stunde | Sonnenuntergang -> pos 5 array + for (my $i = 0;$i < 5;$i++) { # Jahr, Monat, Tag, Stunde, Minute + $set = 0 if ($values[$i] ne "$description_all" && $values[$i] ne $timestamp_values[$i]); + } + $set = 0 if ($values[(($dayOfWeek*1) + 7)] eq "0"); # Wochentag + $set = 0 if ($values[5] eq "00" && $timestamp_values[5] ne "00"); # Sekunde (Intervall 60) + $set = 0 if ($values[5] ne "00" && $timestamp_values[5] ne $values[5]); # Sekunde (Intervall 10) + $intervall = 10 if ($values[5] ne "00"); + Log3 $name, 4, "$name: $d - set=$set intervall=$intervall dayOfWeek=$dayOfWeek column array=".(($dayOfWeek*1) + 7)." (".$values[($dayOfWeek*1) + 7].") $values[0]-$values[1]-$values[2] $values[3]:$values[4]:$values[5]"; + if ($set == 1) { + Log3 $name, 4, "$name: $d - set $values[6] $values[7] ($dayOfWeek, $values[0]-$values[1]-$values[2] $values[3]:$values[4]:$values[5])"; + CommandSet($hash, $values[6]." ".$values[7]) if ($values[7] ne "Def"); + $state = "$d set $values[6] $values[7] accomplished"; + if ($values[7] eq "Def") { + if (AttrVal($name, $d."_set", undef)) { + Log3 $name, 5, "$name: $d - exec at command: ".AttrVal($name, $d."_set", undef); + my $ret = AnalyzeCommandChain(undef, SemicolonEscape(AttrVal($name, $d."_set", undef))); + Log3 $name, 3, "$name: $d\_set - ERROR: $ret" if($ret); + } else { + $state = "$d missing userattr to work!"; + } + } + } + } + } + } + + readingsBeginUpdate($hash); + if ($intervall == 60) { + if ($timestamp_values[5] != 0 && $cnt_activ > 0) { + $intervall = 60 - $timestamp_values[5]; + readingsBulkUpdate($hash, "internalTimer" , $intervall, 1); + Log3 $name, 3, "$name: time difference too large! interval=$intervall, Sekunde=$timestamp_values[5]"; + } + } + ## calculated from the starting point at 00 10 20 30 40 50 if Seconds interval active ## + if ($intervall == 10) { + if ($timestamp_values[5] % 10 != 0 && $cnt_activ > 0) { + $intervall = $intervall - ($timestamp_values[5] % 10); + readingsBulkUpdate($hash, "internalTimer" , $intervall, 1); + Log3 $name, 3, "$name: time difference too large! interval=$intervall, Sekunde=$timestamp_values[5]"; + } + } + $intervall = ($intervall - $microseconds / 1000000); # Korrektur Zeit wegen Drift + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$intervall, "Timer_Check", $hash, 0) if ($cnt_activ > 0); + + $state = "no timer active" if ($cnt_activ == 0 && ReadingsVal($name, "internalTimer", "stop") ne "stop"); + readingsBulkUpdate($hash, "state" , "$state", 1) if defined($state); + readingsBulkUpdate($hash, "internalTimer" , "stop") if ($cnt_activ == 0 && ReadingsVal($name, "internalTimer", "stop") ne "stop"); + readingsBulkUpdate($hash, "internalTimer" , $intervall, 0) if($cnt_activ > 0); + readingsEndUpdate($hash, 1); +} + +# Eval-Rückgabewert für erfolgreiches +# Laden des Moduls +1; + + +# Beginn der Commandref + +=pod +=item [helper] +=item summary Programmable timer +=item summary_DE Programmierbare Zeitschaltuhr + +=begin html + + +

Timer Modul

+
    +The timer module is a programmable timer.

    +In Frontend you can define new times and actions. The smallest possible definition of an action is a 10 second interval.
    +You can use the dropdown menu to make the settings for the time switch point. Only after clicking on the Save button will the setting be taken over. +In the drop-down list, the numerical values ​​for year / month / day / hour / minute / second are available for selection.

    +In addition, you can use the selection SR and SS in the hour and minute column. These rumps represent the time of sunrise and sunset.
    +For example, if you select at minute SS , you have set the minutes of the sunset as the value. As soon as you set the value to SS at hour and minute +the timer uses the calculated sunset time at your location. (For this calculation the FHEM global attributes latitude and longitude are necessary!) + +

    +Programmable actions are currently:
    +
      +
    • on | off - The states must be supported by the device
    • +
    • Def - for a PERL code or a FHEM command *

      +
        example for Def: +
      • { Log 1, "Timer: now switch" } (PERL code)
      • +
      • update (FHEM command)
      +
    • trigger Timer state:ins Log geschrieben (FHEM-command)
    • + +
    +
    + +* To do this, enter the code to be executed in the respective attribute. example: Timer_03_set + +

    +Interval switching of the timer is only possible in the following variants:
    +
    • minute, define second and set all other values ​​(minute, hour, day, month, year) to all
    • +
    • hourly, define second + minute and set all other values ​​(hour, day, month, year) to all
    • +
    • daily, define second + minute + hour and set all other values ​​(day, month, year) to all
    • +
    • monthly, define second + minute + hour + day and set all other values ​​(month, year) to all
    • +
    • annually, define second + minute + hour + day + month and set the value (year) to all
    • +
    • sunrise, define second & define minute + hour with SR and set all other values ​​(day, month, year) to all
    • +
    • sunset, define second & define minute + hour with SS and set all other values ​​(day, month, year) to all
    +

    + +Define
    +
      define <NAME> Timer

      + example: +
        + define timer Timer +
      +

    + +Set
    +
      + +
    • addTimer: Adds a new timer
    • + +
    • deleteTimer: Deletes the selected timer
    • + +
    • saveTimers: Saves the settings in file Timers.txt on directory ./FHEM/lib.
    • + +
    • sortTimer: Sorts the saved timers alphabetically.
    • +


    + +Get
    +
      + +
    • loadTimers: Loads a saved configuration from file Timers.txt from directory ./FHEM/lib.
    • +


    + +Attribute
    +
    +
    • stateFormat
      + It is used to format the value state
      + example: { ReadingsTimestamp($name, "state", 0) ."&nbsp;- ". ReadingsVal($name, "state", "none");}
      +                - will format to output: 2019-09-19 17:51:44 - Timer_04 saved

    +
    +
    +
    • Table_Header_with_time
      + Shows or hides the sunrise and sunset with the local time above the table. (on | off, standard off)

    +
    • Table_Size_TextBox
      + Correction value to change the length of the text box for the device name / designation. (default 20)

    +
    • Table_Style
      + Turns on the defined table style. (on | off, default off)

    +
    • Table_View_in_room
      + Toggles the tables UI in the room view on or off. (on | off, standard on)
      + In the room Unsorted the table UI is always switched off!

    +
    • Timer_preselection
      + Sets the input values ​​for a new timer to the current time. (on | off = default)

    +
    • Show_DeviceInfo
      + Shows the additional information (alias | comment, default off)

    +
    + +Generierte Readings
    +
    • Timer_xx
      + Memory values ​​of the individual timer

    • +
    • internalTimer
      + State of the internal timer (stop or Interval until the next call)


    + +Hints:
    +
    • Entries in the system logfile like: 2019.09.20 22:15:01 3: Timer: time difference too large! interval=59, Sekunde=01 say that the timer has recalculated the time.
    + +
+=end html + + +=begin html_DE + + +

Timer Modul

+
    +Das Timer Modul ist eine programmierbare Schaltuhr.

    +Im Frontend können Sie neue Zeitpunkte und Aktionen definieren. Die kleinstmögliche Definition einer Aktion ist ein 10 Sekunden Intervall.
    +Mittels der Dropdown Menüs können Sie die Einstellungen für den Zeitschaltpunkt vornehmen. Erst nach dem drücken auf den Speichern Knopf wird die Einstellung übernommen.

    +In der DropDown-Liste stehen jeweils die Zahlenwerte für Jahr / Monat / Tag / Stunde / Minute / Sekunde zur Auswahl.
    +Zusätzlich können Sie in der Spalte Stunde und Minute die Auswahl SA und SU nutzen. Diese Kürzel stehen für den Zeitpunkt Sonnenaufgang und Sonnenuntergang.
    +Wenn sie Beispielsweise bei Minute SU auswählen, so haben Sie die Minuten des Sonnenuntergang als Wert gesetzt. Sobald Sie bei Stunde und Minute den Wert auf SU +stellen, so nutzt der Timer den errechnenten Zeitpunkt Sonnenuntergang an Ihrem Standort. (Für diese Berechnung sind die FHEM globalen Attribute latitude und longitude notwendig!) + +

    +Programmierbare Aktionen sind derzeit:
    +
      +
    • on | off - Die Zustände müssen von dem zu schaltenden Device unterstützt werden
    • +
    • Def - für einen PERL-Code oder ein FHEM Kommando *

      +
        Beispiele für Def: +
      • { Log 1, "Timer: schaltet jetzt" } (PERL-Code)
      • +
      • update (FHEM-Kommando)
      • +
      • trigger Timer state:ins Log geschrieben (FHEM-Kommando)
      +
    • +
    +
    + +* Hierfür hinterlegen Sie den auszuführenden Code in das jeweilige Attribut. Bsp.: Timer_03_set + +

    +Eine Intervallschaltung des Timer ist nur möglich in folgenden Varianten:
    +
    • minütlich, Sekunde definieren und alle anderen Werte (Minute, Stunde, Tag, Monat, Jahr) auf alle setzen
    • +
    • stündlich, Sekunde + Minute definieren und alle anderen Werte (Stunde, Tag, Monat, Jahr) auf alle setzen
    • +
    • täglich, Sekunde + Minute + Stunde definieren und alle anderen Werte (Tag, Monat, Jahr) auf alle setzen
    • +
    • monatlich, Sekunde + Minute + Stunde + Tag definieren und alle anderen Werte (Monat, Jahr) auf alle setzen
    • +
    • jährlich, Sekunde + Minute + Stunde + Tag + Monat definieren und den Wert (Jahr) auf alle setzen
    • +
    • Sonnenaufgang, Sekunde definieren & Minute + Stunde definieren mit SA und alle anderen Werte (Tag, Monat, Jahr) auf alle setzen
    • +
    • Sonnenuntergang, Sekunde definieren & Minute + Stunde definieren mit SU und alle anderen Werte (Tag, Monat, Jahr) auf alle setzen
    +

    + +Define
    +
      define <NAME> Timer

      + Beispiel: +
        + define Schaltuhr Timer +
      +

    + +Set
    +
      + +
    • addTimer: Fügt einen neuen Timer hinzu.
    • + +
    • deleteTimer: Löscht den ausgewählten Timer.
    • + +
    • saveTimers: Speichert die eingestellten Timer in der Datei Timers.txt im Verzeichnis ./FHEM/lib.
    • + +
    • sortTimer: Sortiert die gespeicherten Timer alphabetisch.
    • +


    + +Get
    +
      + +
    • loadTimers: Läd eine gespeicherte Konfiguration aus der Datei Timers.txt aus dem Verzeichnis ./FHEM/lib.
    • +


    + +Attribute
    +
    +
    • stateFormat
      + Es dient zur Formatierung des Wertes state
      + Beispiel: { ReadingsTimestamp($name, "state", 0) ."&nbsp;- ". ReadingsVal($name, "state", "none");}
      +                - wird zur formatieren Ausgabe: 2019-09-19 17:51:44 - Timer_04 saved

    +
    • Table_Border
      + Blendet den Tabellenrahmen ein. (on | off, standard off)

    +
    +
    • Table_Header_with_time
      + Blendet den Sonnenauf und Sonnenuntergang mit der lokalen Zeit über der Tabelle ein oder aus. (on | off, standard off)

    +
    • Table_Size_TextBox
      + Korrekturwert um die Länge der Textbox für die Gerätenamen / Bezeichung zu verändern. (standard 20)

    +
    • Table_Style
      + Schaltet den definierten Tabellen-Style ein. (on | off, standard off)

    +
    • Table_View_in_room
      + Schaltet das Tabellen UI in der Raumansicht an oder aus. (on | off, standard on)
      + Im Raum Unsorted ist das Tabellen UI immer abgeschalten!

    +
    • Timer_preselection
      + Setzt die Eingabewerte bei einem neuen Timer auf die aktuelle Zeit. (on | off, standard off)

    +
    • Show_DeviceInfo
      + Blendet die Zusatzinformation ein. (alias | comment, standard off)

    +
    + +Generierte Readings
    +
    • Timer_xx
      + Speicherwerte des einzelnen Timers

    • +
    • internalTimer
      + Zustand des internen Timers (stop oder Intervall bis zum nächsten Aufruf)


    + +Hinweise:
    +
    • Einträge im Systemlogfile wie: 2019.09.20 22:15:01 3: Timer: time difference too large! interval=59, Sekunde=01 sagen aus, das der Timer die Zeit neu berechnet hat.
    + +
+=end html_DE + +# Ende der Commandref +=cut \ No newline at end of file diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index f598639a4..431c5a12d 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -411,6 +411,7 @@ FHEM/88_IPWE.pm tdressler Sonstiges FHEM/88_Itach_IRDevice ulimaass Sonstige Systeme FHEM/88_Itach_Relay.pm jeschkec Automatisierung FHEM/88_LINDY_HDMI_SWITCH.pm jeschkec Multimedia +FHEM/88_Timer.pm HomeAuto_User/elektron-bbs Automatisierung FHEM/88_VantagePro2.pm jeschkec Sonstiges FHEM/88_WEBCOUNT.pm jeschkec Sonstiges FHEM/88_xs1Bridge.pm HomeAuto_User Sonstige Systeme