mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-25 03:44:52 +00:00
1615 lines
77 KiB
Perl
1615 lines
77 KiB
Perl
#################################################################
|
||
# $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.html | https://forum.fhem.de/index.php/topic,103986.0.html
|
||
#
|
||
# 2019 - ... HomeAuto_User, elektron-bbs
|
||
#################################################################
|
||
# notes:
|
||
# - module mit package umsetzen
|
||
#################################################################
|
||
|
||
|
||
package main;
|
||
|
||
use strict;
|
||
use warnings;
|
||
use Time::HiRes qw(gettimeofday);
|
||
use FHEM::Meta;
|
||
|
||
my @action = qw(on off DEF);
|
||
my @names;
|
||
my @designations;
|
||
my $description_all;
|
||
my $cnt_attr_userattr = 0;
|
||
my $language = uc(AttrVal('global', 'language', 'EN'));
|
||
|
||
if ($language eq 'DE') {
|
||
@designations = ('Sonnenaufgang','Sonnenuntergang','lokale Zeit','Uhr','SA','SU','Einstellungen speichern');
|
||
$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','Offset');
|
||
} else {
|
||
@designations = ('Sunrise','Sunset','local time','','SR','SS','save settings');
|
||
$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','offset');
|
||
}
|
||
|
||
##########################
|
||
sub Timer_Initialize {
|
||
my ($hash) = @_;
|
||
|
||
$hash->{AttrFn} = 'Timer_Attr';
|
||
$hash->{AttrList} = 'disable:0,1 '.
|
||
'Offset_Horizon:HORIZON=-0.833,REAL,CIVIL,NAUTIC,ASTRONOMIC '.
|
||
'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,55,60,65,70,75,80,85 '.
|
||
'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
|
||
|
||
return FHEM::Meta::InitMod( __FILE__, $hash );
|
||
}
|
||
|
||
##########################
|
||
# 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";
|
||
|
||
# Anzeigen der Modulversion (Internal FVERSION) über FHEM::Meta, Variable in META.json Abschnitt erforderlich: "version": "v1.0.0", siehe https://wiki.fhem.de/wiki/Meta
|
||
return $@ unless ( FHEM::Meta::SetInternals($hash) );
|
||
|
||
if (@arg != 2) { return "Usage: define <name> $name"; }
|
||
|
||
if ($init_done) {
|
||
if (!defined(AttrVal($autocreateName, 'disable', undef)) && !exists($defs{$filelogName})) {
|
||
### create FileLog ###
|
||
if (defined AttrVal($autocreateName, 'filelog', undef)) { $autocreateFilelog = AttrVal($autocreateName, 'filelog', undef); }
|
||
$autocreateFilelog =~ s/%NAME/$name/gxms;
|
||
$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 ###
|
||
if (!defined AttrVal($name, 'room', undef)) { CommandAttr($hash,"$name room $typ"); } # set room, if only undef --> new def
|
||
}
|
||
|
||
### default value´s ###
|
||
readingsBeginUpdate($hash);
|
||
readingsBulkUpdate($hash, 'state' , 'Defined');
|
||
readingsBulkUpdate($hash, 'internalTimer' , 'stop');
|
||
readingsEndUpdate($hash, 0);
|
||
return;
|
||
}
|
||
|
||
#####################
|
||
sub Timer_Set {
|
||
my ( $hash, $name, @a ) = @_;
|
||
if (int(@a) < 1) { return 'no set value specified'; }
|
||
|
||
my $setList = 'addTimer:noArg ';
|
||
my $cmd = $a[0];
|
||
my $cmd2 = $a[1];
|
||
my $Timers_Count = 0;
|
||
my $Timers_Count2;
|
||
my $Timers_Nrs = '';
|
||
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)+$/xms) {
|
||
$Timers_Count++;
|
||
$d =~ s/Timer_//ms;
|
||
$Timers_Nrs.= $d.',';
|
||
}
|
||
}
|
||
chop $Timers_Nrs;
|
||
|
||
if ($Timers_Count > 0) {
|
||
$setList.= 'deleteTimer:'.$Timers_Nrs.' ';
|
||
$setList.= 'onTimer:'.$Timers_Nrs.' ';
|
||
$setList.= 'offTimer:'.$Timers_Nrs.' ';
|
||
$setList.= 'onTimerAll:noArg ';
|
||
$setList.= 'offTimerAll:noArg ';
|
||
$setList.= 'saveTimers:noArg';
|
||
if ($Timers_Count > 1) { $setList.= ' sortTimer:noArg'; }
|
||
}
|
||
|
||
if ($cmd ne '?') { Log3 $name, 4, "$name: Set | cmd=$cmd"; }
|
||
|
||
##### START cmd´s #####
|
||
if ($cmd =~ /o(n|ff)Timer$/) {
|
||
my @ar = split(',' , ReadingsVal($name, 'Timer_'.$cmd2, 0));
|
||
$ar[15] = $cmd eq 'onTimer' ? 1 : 0 ;
|
||
my $reading = '';
|
||
for (my $x = 0; $x < scalar @ar; $x++) {
|
||
$reading .= $ar[$x] . ',';
|
||
}
|
||
chop $reading;
|
||
my $state = $cmd eq 'onTimer' ? 'on' : 'off' ;
|
||
readingsBeginUpdate($hash);
|
||
readingsBulkUpdate($hash, 'Timer_'.$cmd2 , $reading);
|
||
readingsBulkUpdate($hash, 'state' , "Timer_$cmd2 switched $state");
|
||
readingsEndUpdate($hash, 1);
|
||
InternalTimer(gettimeofday(), 'Timer_Check', $hash, 0);
|
||
}
|
||
|
||
if ($cmd =~ /TimerAll$/) {
|
||
readingsBeginUpdate($hash);
|
||
foreach my $d (sort keys %{$hash->{READINGS}}) {
|
||
if ($d =~ /^Timer_(\d)+$/xms) {
|
||
my @ar = split(',' , ReadingsVal($name, $d, 0));
|
||
$ar[15] = substr($cmd,0,2) eq 'on' ? 1 : 0 ;
|
||
my $reading = '';
|
||
for (my $x = 0; $x < scalar @ar; $x++) {
|
||
$reading .= $ar[$x] . ',';
|
||
}
|
||
chop $reading;
|
||
readingsBulkUpdate($hash, $d , $reading);
|
||
}
|
||
}
|
||
readingsEndUpdate($hash, 1);
|
||
|
||
if ($cmd eq 'onTimerAll') {
|
||
InternalTimer(gettimeofday(), 'Timer_Check', $hash, 0);
|
||
readingsSingleUpdate($hash, 'state' , "all Timers on", 1);
|
||
}
|
||
if ($cmd eq 'offTimerAll') {
|
||
RemoveInternalTimer($hash, 'Timer_Check');
|
||
readingsSingleUpdate($hash, 'state' , "all Timers off", 1);
|
||
}
|
||
}
|
||
|
||
if ($cmd eq 'sortTimer') {
|
||
my @timers_unsortet;
|
||
my @userattr_values;
|
||
my @attr_values_names;
|
||
my $timer_nr_new;
|
||
my $array_diff = 0; # difference, Timer can be sorted >= 1
|
||
my $array_diff_cnt1 = 0; # need to check 1 + 1
|
||
my $array_diff_cnt2 = 0; # need to check 1 + 1
|
||
|
||
foreach my $readingsName (sort keys %{$hash->{READINGS}}) {
|
||
if ($readingsName =~ /^Timer_(\d+)$/xms) {
|
||
$value = ReadingsVal($name, $readingsName, 0);
|
||
$value =~ /^.*\d{2},(.*),(on|off|DEF)/xms;
|
||
push(@timers_unsortet,$1.','.ReadingsVal($name, $readingsName, 0).",$readingsName"); # unsort Reading Wert in Array
|
||
$array_diff_cnt1++;
|
||
$array_diff_cnt2 = substr($readingsName,-2) * 1;
|
||
if ($array_diff_cnt1 != $array_diff_cnt2 && $array_diff == 0) { $array_diff = 1; }
|
||
}
|
||
}
|
||
|
||
my @timers_sort = sort @timers_unsortet; # Timer in neues Array sortieren
|
||
|
||
for (my $i=0; $i<scalar(@timers_unsortet); $i++) {
|
||
if ($timers_unsortet[$i] ne $timers_sort[$i]) { $array_diff++; }
|
||
}
|
||
|
||
if ($array_diff != 0) { RemoveInternalTimer($hash, 'Timer_Check'); }
|
||
if ($array_diff == 0) { return 'cancellation! No sorting necessary.'; } # check, need action continues
|
||
|
||
for (my $i=0; $i<scalar(@timers_sort); $i++) {
|
||
readingsDelete($hash, substr($timers_sort[$i],-8)); # Readings Timer loeschen
|
||
}
|
||
|
||
for (my $i=0; $i<scalar(@timers_sort); $i++) {
|
||
$timer_nr_new = sprintf("%02s",$i + 1); # neue Timer-Nummer
|
||
if ($timers_sort[$i] =~ /^.*\d{2},(.*),(DEF),.*,(Timer_\d+)/xms) { # filtre DEF values - Perl Code (DEF must in S2 - Timer nr old $3)
|
||
Log3 $name, 4, "$name: Set | $cmd: ".$timers_sort[$i];
|
||
if (defined AttrVal($name, $3.'_set', undef)) {
|
||
Log3 $name, 4, "$name: Set | $cmd: ".$3.' remember values';
|
||
push(@userattr_values,"Timer_$timer_nr_new".','.AttrVal($name, $3.'_set',0)); # userattr value in Array with new numbre
|
||
}
|
||
Timer_delFromUserattr($hash,$3.'_set:textField-long'); # delete from userattr (old numbre)
|
||
Log3 $name, 4, "$name: Set | $cmd: added to array attr_values_names -> "."Timer_$timer_nr_new".'_set:textField-long';
|
||
push(@attr_values_names, "Timer_$timer_nr_new".'_set:textField-long');
|
||
}
|
||
$timers_sort[$i] = substr( substr($timers_sort[$i],index($timers_sort[$i],',')+1) ,0,-9);
|
||
readingsSingleUpdate($hash, 'Timer_'.$timer_nr_new , $timers_sort[$i], 1);
|
||
}
|
||
|
||
for (my $i=0; $i<scalar(@attr_values_names); $i++) {
|
||
Log3 $name, 4, "$name: Set | $cmd: from array to attrib userattr -> $attr_values_names[$i]";
|
||
addToDevAttrList($name,$attr_values_names[$i]); # added to userattr (new numbre)
|
||
}
|
||
|
||
addStructChange('modify', $name, "attr $name userattr"); # note with question mark
|
||
|
||
if (scalar(@userattr_values) > 0) { # write userattr_values
|
||
for (my $i=0; $i<scalar(@userattr_values); $i++) {
|
||
my $timer_nr = substr($userattr_values[$i],0,8).'_set';
|
||
my $value_attr = substr($userattr_values[$i],index($userattr_values[$i],',')+1);
|
||
CommandAttr($hash,"$name $timer_nr $value_attr");
|
||
}
|
||
}
|
||
Timer_Check($hash);
|
||
}
|
||
|
||
if ($cmd eq 'addTimer') {
|
||
$Timers_Count = 0;
|
||
foreach my $d (sort keys %{$hash->{READINGS}}) {
|
||
if ($d =~ /^Timer_(\d+)$/xms) {
|
||
$Timers_Count++;
|
||
$Timers_Count2 = $1 * 1;
|
||
if ($Timers_Count != $Timers_Count2 && $Timers_diff == 0) { # only for diff
|
||
$Timers_diff++;
|
||
last;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($Timers_Count > 99) { return 'ERROR: The limit of timers has been reached. More than 99 Timers are not supported!' };
|
||
|
||
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";
|
||
}
|
||
|
||
if ($Timers_diff == 0) { $Timers_Count = $Timers_Count + 1; }
|
||
readingsSingleUpdate($hash, 'Timer_'.sprintf("%02s", $Timers_Count) , $value, 1);
|
||
}
|
||
|
||
if ($cmd eq 'saveTimers') {
|
||
open(my $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+)$/xms);
|
||
}
|
||
|
||
print $SaveDoc "\n";
|
||
|
||
foreach my $e (sort keys %{$attr{$name}}) {
|
||
my $LE = '';
|
||
if (AttrVal($name, $e, undef) !~ /\n$/xms) { $LE = "\n"; }
|
||
if ($e =~ /^Timer_(\d+)_set$/xms) { print $SaveDoc $e.','.AttrVal($name, $e, undef).$LE; }
|
||
}
|
||
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);
|
||
Timer_PawList($hash); # list, Probably associated with
|
||
addStructChange('modify', $name, "attr $name userattr Timer_$cmd2"); # note with question mark
|
||
}
|
||
|
||
if ($a[0] eq '?') { return $setList; }
|
||
if (not grep { /$cmd/xms } $setList) { return "$name unknown argument $cmd, choose one of $setList"; }
|
||
|
||
return;
|
||
}
|
||
|
||
#####################
|
||
sub Timer_Get {
|
||
my ( $hash, $name, $cmd, @a ) = @_;
|
||
my $list = 'loadTimers:no,yes';
|
||
my $cmd2 = $a[0];
|
||
my $Timer_cnt_name = -1;
|
||
my $room = AttrVal($name, 'room', 'Unsorted');
|
||
|
||
if ($cmd eq 'loadTimers') {
|
||
if ($cmd2 eq 'no') { return; }
|
||
if ($cmd2 eq 'yes') {
|
||
my $error = '';
|
||
my $line = 0;
|
||
my $newReading = '';
|
||
my $msg = '';
|
||
my @lines_readings;
|
||
my @attr_values;
|
||
my @attr_values_names;
|
||
RemoveInternalTimer($hash, 'Timer_Check');
|
||
|
||
open (my $InputFile, '<' ,"./FHEM/lib/$name".'_conf.txt') || return "ERROR: No file $name".'_conf.txt found in ./FHEM/lib directory from FHEM!';
|
||
while (<$InputFile>) {
|
||
$line++;
|
||
# 0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9,0,1,2,3,4,5,6,7
|
||
# Timer_04,alle,alle,alle,12,00,00,Update FHEM,Def,1,0,0,0,0,0,0,1 - alt ohne Offset
|
||
# Timer_01,alle,alle,alle,04,01,00,Update FHEM,DEF,1,1,1,1,1,1,1,1,-1440 - neu mit Offset
|
||
if ($_ =~ /^Timer_\d{2},/xms) {
|
||
chomp ($_); # Zeilenende entfernen
|
||
$_ =~ s/,Def,/,DEF,/gxms if ($_ =~ /,Def,/xms); # Compatibility modify Def to DEF
|
||
# push(@lines_readings,$_); # lines in array
|
||
my @values = split(',',$_); # split line in array to check
|
||
# $error.= "Number of variables not equal to 17.\n" if (scalar(@values) != 16);
|
||
push(@attr_values_names, $values[0].'_set') if($values[8] eq 'DEF');
|
||
$newReading = '';
|
||
for (my $i=0;$i<@values;$i++) {
|
||
$error .= "Too few variables in this line (minimum 17 allowed). \n" if (scalar(@values) < 17); # kompatibel zur Version ohne Offset
|
||
$error .= "Too many variables on this line (maximum 18 allowed). \n" if (scalar(@values) > 18); # mit Offset
|
||
if ($i == 0 && $values[0] !~ /^Timer_\d{2}$/xms) { $error.= 'device or designation '.$values[0]." is no $name description \n"; }
|
||
if ($i == 1 && $values[1] !~ /^\d{4}$|^$description_all$/xms) { $error.= 'year '.$values[1]." invalid value \n"; }
|
||
if ($i >= 2 && $i <= 5 && $values[$i] ne $description_all) {
|
||
if ($i >= 2 && $i <= 3 && $values[$i] !~ /^\d{2}$/xms) { $error.= 'value '.$values[$i]." not decimal \n"; }
|
||
if ($i == 2 && (($values[2] * 1) < 1 || ($values[2] * 1) > 12)) { $error.= 'month '.$values[2]." wrong value (allowed 01-12) \n"; }
|
||
if ($i == 3 && (($values[3] * 1) < 1 || ($values[3] * 1) > 31)) { $error.= 'day '.$values[3]." wrong value (allowed 01-31) \n"; }
|
||
if ($i >= 4 && $i <= 5 && $values[$i] ne $designations[4] && $values[$i] ne $designations[5]) { # SA -> 4 SU -> 5
|
||
if ($i >= 4 && $i <= 5 && $values[$i] !~ /^\d{2}$/xms) { $error.= 'variable '.$values[$i]." not support \n"; }
|
||
if ($i == 4 && ($values[4] * 1) > 23) { $error.= 'hour '.$values[4]." wrong value (allowed 00-23) \n"; }
|
||
if ($i == 5 && ($values[5] * 1) > 59) { $error.= 'minute '.$values[5]." wrong value (allowed 00-59) \n"; }
|
||
}
|
||
}
|
||
if ($i == 6 && ($values[$i] > 50 || $values[$i] % 10 != 0 || $values[$i] !~ /^\d+$/xms)) { $error.= 'second '.$values[$i]." is not in range, must be 00, 10, 20, 30, 40 or 50 \n"; }
|
||
if ($i == 7 && $values[8] ne 'DEF' && IsDevice($values[7]) == 0) { # Gerät oder Bezeichnung
|
||
if ($values[16] == 1) {
|
||
Log3 $name, 3, "$name: loadTimers $values[0], device $values[7] not found, set to not active";
|
||
$msg .= "$values[0], device $values[7] not found, set to not active<br>";
|
||
$values[16] = 0;
|
||
} elsif ($values[16] == 0) {
|
||
Log3 $name, 3, "$name: loadTimers $values[0], device $values[7] not found, is not active";
|
||
$msg .= "$values[0], device $values[7] not found, is not active<br>";
|
||
}
|
||
}
|
||
if ($i == 8 && not grep { $values[$i] eq $_ } @action) { $error.= 'action '.$values[$i]." is not allowed\n"; }
|
||
if ($i >= 9 && $i <= 16 && $values[$i] ne '0' && $values[$i] ne '1') { $error.= 'weekday or active '.$values[$i]." is not 0 or 1 \n"; }
|
||
if ($i == 17 && ($values[$i] < -1440 || $values[$i] > 1440 || $values[$i] !~ /^-?\d+$/xms)) { $error .= 'Offset '.$values[$i]." is not in range from -1440 to 1440 \n"; }
|
||
if ($error ne '') {
|
||
close $InputFile;
|
||
Timer_Check($hash);
|
||
if ($error =~ /-\s(all\s|alle\s|SA\s|SU\s|SR\s|SS\s)/xms) { $error.= "\nYour language is wrong! ($language)"; }
|
||
return "ERROR: your file is NOT valid!\n\nline $line\n$error";
|
||
}
|
||
$newReading .= $values[$i];
|
||
if ($i < @values - 1) {$newReading .= ',';}
|
||
}
|
||
}
|
||
push(@lines_readings, $newReading); # push lines in array
|
||
|
||
if ($_ =~ /^Timer_\d{2}_set,/xms) {
|
||
$Timer_cnt_name++;
|
||
push(@attr_values, substr($_,13));
|
||
} elsif ($_ !~ /^Timer_\d{2},/xms) {
|
||
if ($Timer_cnt_name >= 0) { $attr_values[$Timer_cnt_name].= $_; }
|
||
if ($_ =~ /.*}.*/xms){ # 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
|
||
if ($d =~ /^Timer_(\d+)$/xms) { readingsDelete($hash, $d); }
|
||
}
|
||
foreach my $f (sort keys %{$attr{$name}}) { # delete all attributes Timer_xx_set ...
|
||
if ($f =~ /^Timer_(\d+)_set$/xms) { CommandDeleteAttr($hash, $name.' '.$f); }
|
||
}
|
||
$hash->{loadTimers} = 1; # Log-Ausgabe unterdrücken
|
||
my @userattr_values = split(' ', AttrVal($name, 'userattr', 'none'));
|
||
for (my $i=0;$i<@userattr_values;$i++) { # delete userattr values Timer_xx_set:textField-long ...
|
||
if ($userattr_values[$i] =~ /^Timer_(\d+)_set:textField-long$/xms) { delFromDevAttrList($name, $userattr_values[$i]); }
|
||
}
|
||
delete($hash->{'loadTimers'});
|
||
foreach my $e (@lines_readings) { # write new readings
|
||
if ($e =~ /^Timer_\d{2},/xms) { readingsSingleUpdate($hash, substr($e,0,8) , substr($e,9,length($e)-9), 1); }
|
||
}
|
||
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
|
||
chomp ($attr_values[$i]); # Zeilenende entfernen
|
||
if ($attr_values_names[$i]) { CommandAttr($hash,"$name $attr_values_names[$i] $attr_values[$i]"); }
|
||
}
|
||
|
||
Timer_PawList($hash); # list, Probably associated with
|
||
readingsSingleUpdate($hash, 'state' , 'Timers loaded', 1);
|
||
FW_directNotify("FILTER=(room=$room|$name)", "#FHEMWEB:WEB", "location.reload('true')", '');
|
||
Timer_Check($hash);
|
||
if ($msg ne '') { # PopUp ausgeben
|
||
$hash->{msg} = $msg;
|
||
InternalTimer(gettimeofday() + 1, 'Timer_OkDialog', $hash); # msg 1 Sekunde verzögert ausgeben (wegen location.reload)
|
||
}
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
return "Unknown argument $cmd, choose one of $list";
|
||
}
|
||
|
||
sub Timer_OkDialog {
|
||
my ($hash) = @_;
|
||
my $name = $hash->{NAME};
|
||
my $msg = $hash->{msg};
|
||
# Log3 $name, 1, "$name: Timer_OkDialog $msg";
|
||
RemoveInternalTimer($hash, 'Timer_OkDialog');
|
||
FW_directNotify("FILTER=$name", '#FHEMWEB:WEB', "FW_okDialog('$msg')", "");
|
||
delete($hash->{'msg'});
|
||
return;
|
||
}
|
||
|
||
|
||
#####################
|
||
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, 5, "$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$/xms) {
|
||
my $err = perlSyntaxCheck($attrValue, ()); # check PERL Code
|
||
InternalTimer(gettimeofday()+0.1, 'Timer_PawList', $hash);
|
||
if ($err) { return $err; }
|
||
}
|
||
}
|
||
|
||
if ($cmd eq 'del') {
|
||
Log3 $name, 5, "$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++;
|
||
if ($cnt_attr_userattr == 1) { return 'Please execute again if you want to force the attribute to delete!'; }
|
||
$cnt_attr_userattr = 0;
|
||
}
|
||
}
|
||
|
||
if ($attrName =~ /^Timer_\d{2}_set$/xms) {
|
||
if (defined $hash->{loadTimers}) { # nicht bei get loadTimers
|
||
Log3 $name, 3, "$name: Attr | Attributes $attrName deleted";
|
||
}
|
||
InternalTimer(gettimeofday()+0.1, 'Timer_PawList', $hash);
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
#####################
|
||
sub Timer_Undef {
|
||
my ($hash, $arg) = @_;
|
||
|
||
RemoveInternalTimer($hash, 'Timer_Check');
|
||
|
||
return;
|
||
}
|
||
|
||
#####################
|
||
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$/xms, @{$events}) && $typ eq "Timer") {
|
||
Log3 $name, 5, "$name: Notify | running and starting $name";
|
||
|
||
### Compatibility Check Def to DEF ###
|
||
Log3 $name, 4, "$name: Notify Compatibility check";
|
||
foreach my $d (keys %{$hash->{READINGS}}) {
|
||
if ( $d =~ /^Timer_\d+$/xms && ReadingsVal($name, $d, '') =~ /,Def,/xms ) {
|
||
Log3 $name, 4, "$name: $d with ".ReadingsVal($name, $d, '').' must modify!';
|
||
my $ReadingsMod = ReadingsVal($name, $d, '');
|
||
$ReadingsMod =~ s/,Def,/,DEF,/gxms;
|
||
readingsSingleUpdate($hash, $d , $ReadingsMod, 0);
|
||
}
|
||
}
|
||
Timer_Check($hash);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
##### 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',90);
|
||
my $Table_Style = AttrVal($name,'Table_Style','off');
|
||
my $Table_View_in_room = AttrVal($name,'Table_View_in_room','on');
|
||
my $style_code1 = '';
|
||
my $style_code2 = '';
|
||
my $horizon = AttrVal($name,'Offset_Horizon','REAL');
|
||
my $FW_room_dupl = $FW_room;
|
||
my @timer_nr;
|
||
my $cnt_max = scalar(@names);
|
||
my $timer_nr_all = '';
|
||
|
||
if ((!AttrVal($name, 'room', undef) && $FW_detail eq '') || ($Table_View_in_room eq 'off' && $FW_detail eq '')) { return $html; }
|
||
|
||
if ($Table_Style eq 'on') {
|
||
### style via CSS for Checkbox ###
|
||
$html.= '<style>
|
||
/* Labels for checked inputs */
|
||
input:checked {
|
||
}
|
||
|
||
/* Checkbox element, when checked */
|
||
input[type="checkbox"]:checked {
|
||
box-shadow: 2px -2px 1px #13ab15;
|
||
-moz-box-shadow: 2px -2px 1px #13ab15;
|
||
-webkit-box-shadow: 2px -2px 1px #13ab15;
|
||
-o-box-shadow: 2px -2px 1px #13ab15;
|
||
}
|
||
|
||
/* Checkbox element, when NO checked */
|
||
input[type="checkbox"] {
|
||
box-shadow: 2px -2px 1px #b5b5b5;
|
||
-moz-box-shadow: 2px -2px 1px #b5b5b5;
|
||
-webkit-box-shadow: 2px -2px 1px #b5b5b5;
|
||
-o-box-shadow: 2px -2px 1px #b5b5b5;
|
||
}
|
||
|
||
/* Checkbox element, when checked and hover */
|
||
input:hover[type="checkbox"]{
|
||
box-shadow: 2px -2px 1px red;
|
||
-moz-box-shadow: 2px -2px 1px red;
|
||
-webkit-box-shadow: 2px -2px 1px red;
|
||
-o-box-shadow: 2px -2px 1px red;
|
||
}
|
||
|
||
/* Save element */
|
||
input[type="reset"] {
|
||
border-radius:4px;
|
||
}
|
||
|
||
</style>';
|
||
}
|
||
|
||
my $offset = 0;
|
||
foreach my $d (sort keys %{$hash->{READINGS}}) {
|
||
if ($d =~ /^Timer_\d+$/xms) {
|
||
$Timers_Count++;
|
||
push(@timer_nr, substr($d,index($d,'_')+1));
|
||
my @ar = split(',', ReadingsVal($name, $d, undef));
|
||
if (substr($ar[3], 0, 1) eq 'S' || substr($ar[4], 0, 1) eq 'S') { # Stunde || Minute beginnt mit "S"
|
||
$offset += 1; # Zähler für Anzeige "Offset" in der Überschrift der Tabelle
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($Table_Border eq 'on') { $style_code2 = "border:2px solid #00FF00;"; }
|
||
|
||
$html.= "<div style=\"text-align: center; font-size:medium; padding: 0px 0px 6px 0px;\">$designations[0]: ".sunrise_abs($horizon)." $designations[3] | $designations[1]: ".sunset_abs($horizon)." $designations[3] | $designations[2]: ".TimeNow()." $designations[3]</div>" if($Table_Header_with_time eq 'on');
|
||
$html.= "<div id=\"table\"><table id=\"tab\" class=\"block wide\" style=\"$style_code2\">";
|
||
|
||
# Timer Jahr Monat Tag Stunde Minute Sekunde Gerät Aktion Mo Di Mi Do Fr Sa So aktiv Offset
|
||
# ----------------------------------------------------------------------------------------------
|
||
# 2019 09 03 18 15 00 Player on 0 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)
|
||
# Button SPEICHERN
|
||
|
||
## Ueberschrift
|
||
$html.= "<tr class=\"odd\">";
|
||
####
|
||
|
||
if ($Table_Border_Cell eq 'on') { $style_code1 = "border:1px solid #D8D8D8;"; }
|
||
|
||
for(my $spalte = 0; $spalte <= $cnt_max - 1; $spalte++) {
|
||
# auto Breite | Timer-Nummer
|
||
if ($spalte == 0) { $html.= "<td align=\"center\" style=\"$style_code1 Padding-top:3px; Padding-left:5px; text-decoration:underline\">".$names[$spalte]."</td>"; }
|
||
# definierte Breite | Dropdown-Listen Jahr, Monat, Tag, Std, Min, Sek
|
||
if ($spalte >= 1 && $spalte <= 6) { $html.= "<td align=\"center\" width=70 style=\"$style_code1 Padding-top:3px; text-decoration:underline\">".$names[$spalte]."</td>"; }
|
||
# auto Breite | Bezeichnung, Aktion, Mo - So, aktive
|
||
if ($spalte > 6 && $spalte < $cnt_max - 1) { $html.= "<td align=\"center\" style=\"$style_code1 Padding-top:3px; text-decoration:underline\">".$names[$spalte]."</td>"; }
|
||
# auto Breite | Offset
|
||
if ($spalte == $cnt_max - 1) {
|
||
$html .= "<td align=\"center\" style=\"$style_code1 Padding-top:3px; Padding-right:5px; ";
|
||
if ($offset) {
|
||
$html .= "display:table-cell; ";
|
||
} else {
|
||
$html .= "display:none; ";
|
||
}
|
||
$html .= "text-decoration:underline\" id=\"17\">".$names[$spalte]."</td>";
|
||
}
|
||
}
|
||
$html.= "</tr>";
|
||
|
||
for(my $zeile = 0; $zeile < $Timers_Count; $zeile++) {
|
||
$html.= sprintf("<tr class=\"%s\">", ($zeile & 1)?"odd":"even");
|
||
my $id = $timer_nr[$zeile] * 20; # id 20, 40, 60 ...
|
||
$timer_nr_all.= ( $timer_nr[$zeile]*20 + 17 ).',';
|
||
#Log3 $name, 3, "$name: FW_Detail | TimerNr $timer_nr[$zeile], 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++) {
|
||
if ($zeile == $Timers_Count - 1) { $style_code1 .= "Padding-bottom:5px; "; } # letzte Zeile
|
||
|
||
if ($spalte == 1) { $html.= "<td align=\"center\" style=\"$style_code1\">".sprintf("%02s", $timer_nr[$zeile])."</td>"; } # 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
|
||
|
||
if ($spalte == 2) { $start = substr( FmtDateTime(time()) ,0,4); } # Jahr -> von 2016-02-16 19:34:24
|
||
if ($spalte == 2) { $stop = $start + 10; } # Jahr -> maximal 10 Jahre im voraus
|
||
if ($spalte == 3 || $spalte == 4) { $start = 1; } # Monat, Tag
|
||
if ($spalte == 4) { $stop = 31; } # Tag
|
||
if ($spalte == 5) { $stop = 23; } # Stunde
|
||
if ($spalte == 6) { $stop = 59; } # Minute
|
||
|
||
if ($spalte == 7) { $stop = 50; } # Sekunde
|
||
if ($spalte == 7) { $step = 10 ; } # Sekunde
|
||
$id++;
|
||
|
||
# Log3 $name, 3, "$name: FW_Detail | Zeile $zeile, id $id, select";
|
||
$html.= "<td align=\"center\" style=\"$style_code1\"><select id=\"".$id."\""; # id need for java script
|
||
if ($spalte == 5 || $spalte == 6) { # Stunde, Minute
|
||
$html.= " onchange=\"validColumn(".$id.",".$Timers_Count.")\""; # id need for java script
|
||
}
|
||
$html.= ">";
|
||
if ($spalte <= 6) { $html.= "<option>$description_all</option>"; } # Jahr, Monat, Tag, Stunde, Minute
|
||
if ($spalte == 5 || $spalte == 6) { # Stunde, Minute
|
||
$selected = $select_Value[$spalte-2] eq $designations[4] ? "selected=\"selected\"" : '';
|
||
$html.= "<option $selected value=\"".$designations[4]."\">".$designations[4]."</option>"; # Sonnenaufgang -> pos 4 array
|
||
$selected = $select_Value[$spalte-2] eq $designations[5] ? "selected=\"selected\"" : '';
|
||
$html.= "<option $selected value=\"".$designations[5]."\">".$designations[5]."</option>"; # Sonnenuntergang -> pos 5 array
|
||
}
|
||
for(my $k = $start ; $k <= $stop ; $k += $step) {
|
||
$selected = $select_Value[$spalte-2] eq sprintf("%02s", $k) ? "selected=\"selected\"" : '';
|
||
$html.= "<option $selected value=\"" . sprintf("%02s", $k) . "\">" . sprintf("%02s", $k) . "</option>";
|
||
}
|
||
$html.="</select></td>";
|
||
}
|
||
|
||
if ($spalte == 8) { ## Spalte Geraete
|
||
$id ++;
|
||
my $comment = '';
|
||
if (AttrVal($name,'Show_DeviceInfo','') eq 'alias') { $comment = AttrVal($select_Value[$spalte-2],'alias',''); }
|
||
if (AttrVal($name,'Show_DeviceInfo','') eq 'comment') { $comment = AttrVal($select_Value[$spalte-2],'comment',''); }
|
||
$html.= "<td align=\"center\" style=\"$style_code1\"><input style='width:$Table_Size_TextBox%' type=\"text\" placeholder=\"Timer_".($zeile + 1)."\" id=\"".$id."\" value=\"".$select_Value[$spalte-2]."\"><br><small>$comment</small></td>";
|
||
}
|
||
|
||
if ($spalte == 9) { ## DropDown-Liste Aktion
|
||
$id ++;
|
||
$html.= "<td align=\"center\" style=\"$style_code1\"><select id=\"".$id."\">"; # id need for java script
|
||
foreach (@action) {
|
||
if ($select_Value[$spalte-2] ne $_) { $html.= "<option> $_ </option>"; }
|
||
if ($select_Value[$spalte-2] eq $_) { $html.= "<option selected=\"selected\">".$select_Value[$spalte-2]."</option>"; }
|
||
}
|
||
$html.="</select></td>";
|
||
}
|
||
|
||
if ($spalte > 9 && $spalte < $cnt_max) { ## Spalte Wochentage + aktiv
|
||
$id ++;
|
||
if ($select_Value[$spalte-2] eq '0') { $html.= "<td align=\"center\" style=\"$style_code1\"><input type=\"checkbox\" name=\"days\" id=\"".$id."\" value=\"0\" onclick=\"Checkbox(".$id.")\"></td>"; }
|
||
if ($select_Value[$spalte-2] eq '1') { $html.= "<td align=\"center\" style=\"$style_code1\"><input type=\"checkbox\" name=\"days\" id=\"".$id."\" value=\"1\" onclick=\"Checkbox(".$id.")\" checked></td>"; }
|
||
}
|
||
|
||
if($spalte == 17){ ## Offset (NUR bei SA & SU)
|
||
$id ++;
|
||
$html.= "<td align=\"center\"><input align=\"center\" ";
|
||
if($select_Value[3] eq $designations[4] || $select_Value[3] eq $designations[5] || $select_Value[4] eq $designations[4] || $select_Value[4] eq $designations[5]) { # SA -> 4 SU -> 5
|
||
if (not $select_Value[16]) { $select_Value[16] = 0; }
|
||
$html.= "style='width:4em; text-align:right;' type=\"number\" min=\"-1440\" max=\"1440\" onkeypress=\"return isValidInputKey(event);\" value=\"".$select_Value[16]."\" ";
|
||
} else {
|
||
$html.= "style='width:4em; display:none' value=\"0\" ";
|
||
}
|
||
$html.= "id=\"".$id."\"></td>";
|
||
}
|
||
|
||
if ($select_Value[$spalte-2] && $spalte > 1 && $spalte <= $cnt_max) { Log3 $name, 5, "$name: FW_Detail | Timer=".$timer_nr[$zeile]." ".$names[$spalte-1].'='.$select_Value[$spalte-2]." cnt_max=$cnt_max ($spalte)"; }
|
||
}
|
||
$html.= "</tr>"; ## Zeilenende
|
||
}
|
||
## Button Speichern
|
||
$html.= "<tr><td colspan=\"$cnt_max\" align=\"center\" style=\"$style_code1\"> <INPUT type=\"reset\" style=\"width:30%;\" onclick=\"pushed_savebutton('".substr($timer_nr_all, 0, -1)."')\" value=\"✔ $designations[6]\"/></td></tr>";
|
||
## Tabellenende
|
||
$html.= "</table>";
|
||
## DIV-Container Ende, danach Script
|
||
$html.= '</div>
|
||
|
||
<script>
|
||
/* checkBox Werte von Checkboxen Wochentage */
|
||
function Checkbox(id) {
|
||
var checkBox = document.getElementById(id);
|
||
if (checkBox.checked) {
|
||
checkBox.value = 1;
|
||
} else {
|
||
checkBox.value = 0;
|
||
}
|
||
}
|
||
|
||
/* input numbre - Check Werte | nur 0-9 . - */
|
||
function isValidInputKey(evt){
|
||
var charCode = (evt.which) ? evt.which : evt.keyCode
|
||
return !(charCode > 31 && (charCode < 45 || charCode == 47 || charCode > 57) );
|
||
}
|
||
|
||
/* Check - eingestellter Wert Browser fuer Ein/Ausblendung Eingabefeld zuständig */
|
||
function validColumn(id,TimersCnt) {
|
||
var idOffset = id - id % 20 + 17;
|
||
let column_hide = false;
|
||
var TimersCntOffset = 0;
|
||
|
||
if (document.getElementById(id).value.charAt(0) == "S") {
|
||
console.log("plausible column found with id " + id +" | idOffset: " + idOffset);
|
||
document.getElementById(idOffset).style.display = \'table-cell\';
|
||
column_hide = true;
|
||
} else {
|
||
var idLine = id - id % 20;
|
||
if (document.getElementById(idLine + 4).value.charAt(0) != "S" && document.getElementById(idLine + 5).value.charAt(0) != "S") {
|
||
document.getElementById(idOffset).style.display = \'none\';
|
||
}
|
||
}
|
||
|
||
/* Check - Wert testValues irgendwo in Rest der Tabelle */
|
||
for (var i3 = 1; i3 <= TimersCnt; i3++) {
|
||
var column_hhmm = i3 * 20 + 4;
|
||
if(document.getElementById(column_hhmm).value.charAt(0) == "S" || document.getElementById(column_hhmm + 1).value.charAt(0) == "S") {
|
||
TimersCntOffset++;
|
||
}
|
||
}
|
||
|
||
/* Ausblendung Offset Ueberschrift */
|
||
if(TimersCntOffset == 0){
|
||
document.getElementById(17).style.display = \'none\';
|
||
} else {
|
||
document.getElementById(17).style.display = \'table-cell\';
|
||
}
|
||
}
|
||
|
||
/* Aktion wenn Speichern */
|
||
function pushed_savebutton(id) {
|
||
/* all id´s is passed as a string with comma separation */
|
||
const myIDs = id.split(",");
|
||
|
||
FW_cmd(FW_root+ \'?XHR=1"'.$FW_CSRF.'"&cmd={FW_pushed_savebutton_preparation("'.$name.'","\'+myIDs.length+\'")}\');
|
||
|
||
for (var i2 = 0; i2 < myIDs.length; i2++) {
|
||
var allVals = [];
|
||
var timerNr = (myIDs[i2] - 17) / 20;
|
||
allVals.push(timerNr);
|
||
var start = myIDs[i2] - 17 + 1;
|
||
for(var i = start; i <= myIDs[i2]; i++) {
|
||
allVals.push(document.getElementById(i).value);
|
||
}
|
||
|
||
/* check Semicolon description */
|
||
var n = allVals[7].search(";"); /* return -1 if not found */
|
||
if (n != -1) {
|
||
FW_errmsg("ERROR: Semicolon not allowed in description!", 4000);
|
||
return;
|
||
}
|
||
FW_cmd(FW_root+ \'?XHR=1"'.$FW_CSRF.'"&cmd={FW_pushed_savebutton("'.$name.'","\'+allVals+\'","'.$FW_room_dupl.'")}\');
|
||
}
|
||
}
|
||
|
||
/* Popup DEF - Zusammenbau (PERL -> Rueckgabe -> Javascript) */
|
||
function show_popup(button) {
|
||
FW_cmd(FW_root+\'?cmd={Timer_Popup("'.$name.'","\'+button+\'")}&XHR=1"'.$FW_CSRF.'"\', function(data){popup_return(data)});
|
||
}
|
||
|
||
/* Popup DEF - Aktionen */
|
||
function popup_return(txt) {
|
||
var div = $("<div id=\"popup_return\">");
|
||
$(div).html(txt);
|
||
$("body").append(div);
|
||
var oldPos = $("body").scrollTop();
|
||
|
||
$(div).dialog({
|
||
dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:false,
|
||
maxHeight:$(window).height()*0.95,
|
||
title: " infobox for user ",
|
||
buttons: [
|
||
{text:"close", click:function(){
|
||
$(this).dialog("close");
|
||
$(div).remove();
|
||
location.reload();
|
||
}}]
|
||
});
|
||
}
|
||
</script>';
|
||
|
||
return $html;
|
||
}
|
||
|
||
### to save counter ###
|
||
sub FW_pushed_savebutton_preparation {
|
||
my $name = shift;
|
||
my $ID_cnt = shift;
|
||
my $hash = $defs{$name};
|
||
|
||
Log3 $name, 4, "$name: FW_pushed_savebutton_preparation | timercount is $ID_cnt & set changes,pushed_savebutton_count,refresh_locked,refresh to 0";
|
||
$hash->{helper}->{ID_cnt} = $ID_cnt; # counter for number of timers
|
||
$hash->{helper}->{changes} = 0; # counter for changes on timer from user
|
||
$hash->{helper}->{pushed_savebutton_count} = 0; # counter of position pushed_savebutton function (event "Events" are not in the correct order)
|
||
$hash->{helper}->{refresh_locked} = 0; # marker that no refresh -> popup, refresh closed popup
|
||
$hash->{helper}->{refresh} = 0; # marker that a page must refresh
|
||
|
||
return
|
||
}
|
||
|
||
### for function from pushed_savebutton ###
|
||
sub FW_pushed_savebutton {
|
||
my $name = shift;
|
||
my $hash = $defs{$name};
|
||
my $rowsField = shift; # neu,alle,alle,alle,alle,alle,00,Beispiel,on,0,0,0,0,0,0,0,0
|
||
my @rowsField = split(',' , $rowsField);
|
||
my $FW_room_dupl = shift;
|
||
my $cnt_names = scalar(@rowsField); # counter for name split from string -> $rowsField
|
||
my $room = AttrVal($name, 'room', 'Unsorted');
|
||
my $ReadingsVal; # OldValue from Timer in Reading
|
||
my @timestamp_values = split( /-|\s|:/xms , TimeNow() ); # Time now splitted from -> 2016-02-16 19:34:24
|
||
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]);
|
||
my $readingStr = "";
|
||
|
||
if ($cnt_names > 18) { return 'ERROR: Comma not allowed in description!'; }
|
||
if (not defined $hash->{helper}->{pushed_savebutton_count} || not defined $hash->{helper}->{ID_cnt}) {
|
||
Log3 $name, 2, "$name: FW_pushed_savebutton | cancellation, helper_pushed_savebutton_count ".$hash->{helper}->{pushed_savebutton_count};
|
||
Log3 $name, 2, "$name: FW_pushed_savebutton | cancellation, helper_ID_cnt ".$hash->{helper}->{ID_cnt};
|
||
return 'ERROR: Timer cancellation!';
|
||
}
|
||
Log3 $name, 4, "$name: FW_pushed_savebutton | ReadingsVal set: $rowsField";
|
||
$hash->{helper}->{pushed_savebutton_count}++;
|
||
|
||
my $checkInput = 0;
|
||
if ($rowsField[1] eq $year) {
|
||
if ($rowsField[2] eq $month || ($rowsField[2] eq 'alle' && $month == 12)) {
|
||
if ($rowsField[3] eq $mday || ($rowsField[3] eq 'alle' && $mday == 31)) {
|
||
$checkInput = 1;
|
||
} elsif (length($rowsField[3]) == 2 && $rowsField[3] < $mday) {return 'ERROR: The day is in the past.';}
|
||
} elsif (length($rowsField[2]) == 2 && $rowsField[2] < $month) {return 'ERROR: The month is in the past.';}
|
||
}
|
||
$ReadingsVal = ReadingsVal($name, 'Timer_'.sprintf("%02s", $rowsField[0]), undef);
|
||
if ($ReadingsVal) {
|
||
Log3 $name, 5, "$name: FW_pushed_savebutton | ReadingsVal old: ".$rowsField[0].','.$ReadingsVal.' cnt='.$hash->{helper}->{pushed_savebutton_count};
|
||
|
||
if ($hash->{helper}->{pushed_savebutton_count} && $hash->{helper}->{ID_cnt} && $hash->{helper}->{pushed_savebutton_count} == $hash->{helper}->{ID_cnt}) {
|
||
Log3 $name, 4, "$name: FW_pushed_savebutton | found END with ".$hash->{helper}->{changes}.' changes !!!';
|
||
$hash->{helper}->{refresh}++; # now, browser can be updated to view current values
|
||
}
|
||
|
||
if ($rowsField eq ($rowsField[0].','.$ReadingsVal)) {
|
||
goto ENDPOINT; # no difference, needs no update
|
||
} else {
|
||
Log3 $name, 4, "$name: FW_pushed_savebutton | ReadingsVal ---> ".$rowsField[0]." must SAVE !!!";
|
||
$hash->{helper}->{changes}++;
|
||
}
|
||
}
|
||
|
||
for(my $i = 0;$i < $cnt_names;$i++) {
|
||
Log3 $name, 5, "$name: FW_pushed_savebutton | ".$names[$i].' -> '.$rowsField[$i];
|
||
## to set time to check input ## SA -> pos 4 array | SU -> pos 5 array ##
|
||
if ($i >= 1 && $i <=6 && ( $rowsField[$i] ne $description_all && $rowsField[$i] ne $designations[4] && $rowsField[$i] ne $designations[5] )) {
|
||
if ($i == 6) { $sec = $rowsField[$i]; }
|
||
if ($i == 5) { $min = $rowsField[$i]; }
|
||
if ($i == 4) { $hour = $rowsField[$i]; }
|
||
if ($i == 3) { $mday = $rowsField[$i]; }
|
||
if ($i == 2) { $month = $rowsField[$i]; }
|
||
if ($i == 1) { $year = $rowsField[$i]; }
|
||
}
|
||
|
||
if ($i == 7) {
|
||
Log3 $name, 5, "$name: FW_pushed_savebutton | check: exists device or name -> ".$rowsField[$i];
|
||
if (IsDevice($rowsField[$i]) == 1) {
|
||
Log3 $name, 5, "$name: FW_pushed_savebutton | ".$rowsField[$i].' is checked and exists';
|
||
}
|
||
if ( (IsDevice($rowsField[$i]) == 0) && ($rowsField[$i+1] eq 'on' || $rowsField[$i+1] eq 'off') ) {
|
||
Log3 $name, 5, "$name: FW_pushed_savebutton | ".$rowsField[$i].' is NOT exists';
|
||
return 'ERROR: device not exists or no description! NO timer saved!';
|
||
}
|
||
}
|
||
if ($i == 17) {
|
||
#$rowsField[17] =~ s/-$//gxms; # nur bei input typ=text
|
||
#$rowsField[17] =~ s/^-+/-/gxms; # nur bei input typ=text
|
||
$rowsField[17] *= 1;
|
||
if ($rowsField[17] > 1440) {$rowsField[17] = 1440;} # max. 1 Tag
|
||
if ($rowsField[17] < -1440) {$rowsField[17] = -1440;} # max. 1 Tag
|
||
$rowsField[17] = sprintf "%.0f", $rowsField[17];
|
||
Log3 $name, 4, "$name: FW_pushed_savebutton | offset= ".$rowsField[17];
|
||
}
|
||
if ($i > 0) {$readingStr .= $rowsField[$i] . ','}
|
||
}
|
||
chop($readingStr);
|
||
|
||
my $timeTimer = fhemTimeLocal($sec, $min, $hour, $mday, $month - 1, $year - 1900);
|
||
if ($checkInput == 1) {
|
||
# Log3 $name, 3, "$name: $timeStamp - $timeTimer = $diff";
|
||
if ((time() - $timeTimer) > 0) {
|
||
# Log3 $name, 3, 'ERROR: The time is in the past. Please set a time in the future!';
|
||
return 'ERROR: The time is in the past. Please set a time in the future!';
|
||
}
|
||
if (($timeTimer - time()) < 60) {
|
||
# Log3 $name, 3, 'ERROR: The next switching point is too small!';
|
||
return 'ERROR: The next switching point is too small!';
|
||
}
|
||
}
|
||
|
||
my $oldValue = ReadingsVal($name,'Timer_'.sprintf("%02s", $rowsField[0]) ,0);
|
||
my @Value_split = split(/,/xms , $oldValue);
|
||
$oldValue = $Value_split[7];
|
||
|
||
@Value_split = split(/,/xms , $readingStr);
|
||
my $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", $rowsField[0]);
|
||
$readingStr =~ s/,,/,$replace,/gxms;
|
||
}
|
||
|
||
readingsBeginUpdate($hash);
|
||
readingsBulkUpdate($hash, 'Timer_'.sprintf("%02s", $rowsField[0]) , $readingStr);
|
||
|
||
my $state = 'Timer_'.sprintf("%02s", $rowsField[0]).' saved';
|
||
my $userattrName = 'Timer_'.sprintf("%02s", $rowsField[0]).'_set:textField-long';
|
||
my $popup = 0;
|
||
|
||
if (($oldValue eq 'on' || $oldValue eq 'off') && $newValue eq 'DEF') {
|
||
$state = 'Timer_'.sprintf("%02s", $rowsField[0]).' is saved and revised userattr. Please set DEF in attribute Timer_'.sprintf("%02s", $rowsField[0]).'_set !';
|
||
addToDevAttrList($name,$userattrName);
|
||
addStructChange('modify', $name, "attr $name userattr"); # note with question mark
|
||
$popup++;
|
||
}
|
||
|
||
if ($oldValue eq 'DEF' && ($newValue eq 'on' || $newValue eq 'off')) {
|
||
$state = 'Timer_'.sprintf("%02s", $rowsField[0]).' is saved and deleted from userattr';
|
||
if (AttrVal($name, 'userattr', undef)) { Timer_delFromUserattr($hash,$userattrName); }
|
||
addStructChange('modify', $name, "attr $name userattr"); # note with question mark
|
||
$hash->{helper}->{changes}++;
|
||
}
|
||
|
||
readingsBulkUpdate($hash, 'state' , $state, 1);
|
||
readingsEndUpdate($hash, 1);
|
||
|
||
Timer_PawList($hash); # list, Probably associated with
|
||
|
||
if ($hash->{helper}->{changes} && $hash->{helper}->{changes} != 0) {
|
||
## popup user message DEF (jump to javascript) ##
|
||
if ($popup != 0) {
|
||
Log3 $name, 4, "$name: FW_pushed_savebutton | popup user info for DEF";
|
||
FW_directNotify("FILTER=(room=$room|$name)", "#FHEMWEB:WEB", "show_popup(".$rowsField[0].")", '');
|
||
$hash->{helper}->{refresh_locked}++; # reset, refresh closed popup -> need to right running
|
||
}
|
||
}
|
||
|
||
ENDPOINT: # jump label for timers that have not been changed
|
||
|
||
## refresh site, need for userattr & right view checkboxes ##
|
||
if ($hash->{helper}->{refresh} && $hash->{helper}->{refresh} != 0 && $hash->{helper}->{refresh_locked} == 0) {
|
||
Log3 $name, 4, "$name: FW_pushed_savebutton | refresh site NEED !!!";
|
||
FW_directNotify("FILTER=(room=$room)", "#FHEMWEB:WEB", "location.reload('true')", '');
|
||
}
|
||
|
||
if ($rowsField[16] eq '1' && ReadingsVal($name, 'internalTimer', 'stop') eq 'stop') { Timer_Check($hash); }
|
||
|
||
return;
|
||
}
|
||
|
||
### Popup DEF - Zusammenbau (Rueckgabe -> Javascript) ###
|
||
sub Timer_Popup {
|
||
my $name = shift;
|
||
my $selected_button = shift;
|
||
$selected_button = sprintf("%02s", $selected_button);
|
||
my $ret = '';
|
||
|
||
Log3 $name, 5, "$name: Popup is running";
|
||
|
||
if ($language eq 'DE') {
|
||
$ret.= "<p style=\"text-decoration-line: underline;\">Hinweis:</p>";
|
||
$ret.= "Das Attribut <code>userattr</code> wurde automatisch angepasst.<br>";
|
||
$ret.= "Um <code>DEF</code> zu definieren, geben Sie bitte das FHEM Kommando oder den PERL Code in das Attribut <code>Timer_$selected_button"."_set</code> ein.<br>";
|
||
$ret.= "<small><code>attr $name Timer_$selected_button"."_set \"DEF Code\"</code> (ohne \" \")</small><br><br>";
|
||
$ret.= "Die Eingabe entspricht der FHEM-Befehlszeile im Browser.<br>";
|
||
$ret.= "<p style=\"text-decoration-line: underline;\">DEF Beispiele:</p>";
|
||
$ret.= "• FHEM Kommando: <code>set TYPE=IT:FILTER=room=03_Stube.*:FILTER=state=on off</code><br>";
|
||
$ret.= "• PERL Code: <code>{ Log 1, \"$name: schaltet jetzt\" }</code><br><br>";
|
||
$ret.= "<i>Weitere Beispiele oder Intervallschaltungen finden Sie in der gerätespezifischen Hilfe.</i><br><br>";
|
||
$ret.= "Klicken Sie auf close, um fortzufahren.";
|
||
} else {
|
||
$ret.= "<p style=\"text-decoration-line: underline;\">Note:</p>";
|
||
$ret.= "The attribute <code>userattr</code> has been automatically revised.<br>";
|
||
$ret.= "To define <code>DEF</code>, please input the FHEM command or PERL code in attribute <code>Timer_$selected_button"."_set</code>.<br>";
|
||
$ret.= "<small><code>attr $name Timer_$selected_button"."_set \"DEF code\"</code> (without \" \")</small><br><br>";
|
||
$ret.= "The input is made equal to the FHEM command line in the browser.<br>";
|
||
$ret.= "<p style=\"text-decoration-line: underline;\">DEF examples:</p>";
|
||
$ret.= "• FHEM command: <code>set TYPE=IT:FILTER=room=03_Stube.*:FILTER=state=on off</code><br>";
|
||
$ret.= "• PERL code: <code>{ Log 1, \"$name: switch now\" }</code><br><br>";
|
||
$ret.= "<i>For more examples or interval switching please look into the Device specific help.</i><br><br>";
|
||
$ret.= "Click close to continue.";
|
||
}
|
||
return $ret;
|
||
}
|
||
|
||
### for delete Timer value from userattr ###
|
||
sub Timer_delFromUserattr {
|
||
my $hash = shift;
|
||
my $deleteTimer = shift;
|
||
my $name = $hash->{NAME};
|
||
|
||
if (AttrVal($name, 'userattr', undef) =~ /$deleteTimer/xms) {
|
||
delFromDevAttrList($name, $deleteTimer);
|
||
Log3 $name, 5, "$name: delFromUserattr | delete $deleteTimer from userattr Attributes";
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
### for Check ###
|
||
sub Timer_Check {
|
||
my ($hash) = @_;
|
||
my $name = $hash->{NAME};
|
||
my @timestamp_values = split(/-|\s|:/xms , TimeNow()); # Time now (2016-02-16 19:34:24) splitted in array
|
||
my $dayOfWeek = strftime('%w', localtime); # Wochentag
|
||
|
||
if ($dayOfWeek eq '0') { $dayOfWeek = 7; } # 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 $horizon = AttrVal($name,'Offset_Horizon','REAL');
|
||
my $sunrise = sunrise_abs($horizon); # Sonnenaufgang 06:34:24
|
||
my $sunset = sunset_abs($horizon); # Sonnenuntergang 19:34:24
|
||
my @sunriseValues = split(':' , $sunrise); # Sonnenaufgang splitted in array
|
||
my @sunsetValues = split(':' , $sunset); # Sonnenuntergang splitted in array
|
||
my $state;
|
||
|
||
Log3 $name, 5, "$name: Check | Sonnenaufgang $sunriseValues[0]:$sunriseValues[1]:$sunriseValues[2], Sonnenuntergang $sunsetValues[0]:$sunsetValues[1]:$sunsetValues[2]";
|
||
Log3 $name, 4, "$name: Check | drift $microseconds microSeconds";
|
||
|
||
foreach my $d (sort keys %{$hash->{READINGS}}) {
|
||
if ($d =~ /^Timer_\d+$/xms) {
|
||
my @values = split(',' , $hash->{READINGS}->{$d}->{VAL});
|
||
#Jahr Monat Tag Stunde Minute Sekunde Gerät Aktion Mo Di Mi Do Fr Sa So aktiv Offset
|
||
#alle, alle, alle, alle, alle, 00, BlueRay_Player_LG, on, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||
#0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
||
|
||
my $set = 1;
|
||
|
||
if ($values[15] == 1) { # input checkbox "aktiv"
|
||
$cnt_activ++;
|
||
if ($values[16] && $values[16] != 0) { # input number "Offset"
|
||
# Log3 $name, 3, "$name: $d must calculated, hh:$values[3] mm:$values[4] | offset: $values[16]";
|
||
my $sunriseSeconds = $sunriseValues[0] * 3600 + $sunriseValues[1] * 60 + $sunriseValues[2] + $values[16] * 60;
|
||
while($sunriseSeconds > 86400) {$sunriseSeconds -= 86400;}
|
||
while($sunriseSeconds < 0) {$sunriseSeconds += 86400;}
|
||
$sunriseValues[0] = sprintf("%02d", $sunriseSeconds / 3600); # Stunde
|
||
$sunriseValues[1] = sprintf("%02d", $sunriseSeconds % 3600 / 60); # Minute
|
||
# $sunriseValues[2] = sprintf("%02d", $sunriseSeconds % 60); # Sekunde
|
||
Log3 $name, 4, "$name: $d New switch time with offset, Sonnenaufgang $sunriseValues[0]:$sunriseValues[1]:$sunriseValues[2], Offset $values[16] Minuten";
|
||
my $sunsetSeconds = $sunsetValues[0] * 3600 + $sunsetValues[1] * 60 + $sunsetValues[2] + $values[16] * 60;
|
||
while($sunsetSeconds > 86400) {$sunsetSeconds -= 86400;}
|
||
while($sunsetSeconds < 0) {$sunsetSeconds += 86400;}
|
||
$sunsetValues[0] = sprintf("%02d", $sunsetSeconds / 3600); # Stunde
|
||
$sunsetValues[1] = sprintf("%02d", $sunsetSeconds % 3600 / 60); # Minute
|
||
# $sunsetValues[2] = sprintf("%02d", $sunsetSeconds % 60); # Sekunde
|
||
Log3 $name, 4, "$name: $d New switch time with offset, Sonnenuntergang $sunsetValues[0]:$sunsetValues[1]:$sunsetValues[2], Offset $values[16] Minuten";
|
||
}
|
||
if ($values[3] eq $designations[4]) { $values[3] = $sunriseValues[0]; } # Stunde | Sonnenaufgang -> pos 0 array
|
||
if ($values[4] eq $designations[4]) { $values[4] = $sunriseValues[1]; } # Minute | Sonnenaufgang -> pos 1 array
|
||
if ($values[3] eq $designations[5]) { $values[3] = $sunsetValues[0]; } # Stunde | Sonnenuntergang -> pos 0 array
|
||
if ($values[4] eq $designations[5]) { $values[4] = $sunsetValues[1]; } # Minute | Sonnenuntergang -> pos 1 array
|
||
Log3 $name, 4, "$name: $d - set $values[6] $values[7] ($dayOfWeek, $values[0]-$values[1]-$values[2] $values[3]:$values[4]:$values[5])";
|
||
@sunriseValues = split(':' , $sunrise); # reset Sonnenaufgang (06:34:24) splitted in array
|
||
@sunsetValues = split(':' , $sunset); # reset Sonnenuntergang (19:34:24) splitted in array
|
||
|
||
for (my $i = 0;$i < 5;$i++) { # Jahr, Monat, Tag, Stunde, Minute
|
||
if ($values[$i] ne $description_all && $values[$i] ne $timestamp_values[$i]) { $set = 0; }
|
||
}
|
||
if ($values[(($dayOfWeek*1) + 7)] eq '0') { $set = 0; } # Wochentag
|
||
if ($values[5] eq '00' && $timestamp_values[5] ne '00') { $set = 0; } # Sekunde (Intervall 60)
|
||
if ($values[5] ne '00' && $timestamp_values[5] ne $values[5]) { $set = 0; } # Sekunde (Intervall 10)
|
||
if ($values[5] ne '00') { $intervall = 10; }
|
||
|
||
Log3 $name, 5, "$name: Check | $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: Check | $d - set $values[6] $values[7] ($dayOfWeek, $values[0]-$values[1]-$values[2] $values[3]:$values[4]:$values[5])";
|
||
if ($values[7] ne 'DEF') { CommandSet($hash, $values[6].' '.$values[7]); }
|
||
# $state = "$d set $values[6] $values[7] accomplished";
|
||
readingsSingleUpdate($hash, 'state' , "$d set $values[6] $values[7] accomplished", 1);
|
||
if ($values[7] eq 'DEF') {
|
||
if (AttrVal($name, $d.'_set', undef)) {
|
||
Log3 $name, 5, "$name: Check | $d - exec at command: ".AttrVal($name, $d.'_set', undef);
|
||
my $ret = AnalyzeCommandChain(undef, SemicolonEscape(AttrVal($name, $d.'_set', undef)));
|
||
if ($ret) { Log3 $name, 3, "$name: $d\_set - ERROR: $ret"; }
|
||
} else {
|
||
$state = "$d missing userattr to work!";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($intervall == 60) {
|
||
if ($timestamp_values[5] != 0 && $cnt_activ > 0) {
|
||
$intervall = 60 - $timestamp_values[5];
|
||
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);
|
||
Log3 $name, 3, "$name: time difference too large! interval=$intervall, Sekunde=$timestamp_values[5]";
|
||
}
|
||
}
|
||
$intervall = ($intervall - $microseconds / 1000000); # Korrektur Zeit wegen Drift
|
||
RemoveInternalTimer($hash);
|
||
|
||
readingsBeginUpdate($hash);
|
||
if ($cnt_activ > 0) { InternalTimer(gettimeofday()+$intervall, 'Timer_Check', $hash, 0); }
|
||
if ($cnt_activ == 0 && ReadingsVal($name, 'internalTimer', 'stop') ne 'stop') { $state = 'no timer active'; }
|
||
if (defined $state) { readingsBulkUpdate($hash, 'state' , "$state", 1); }
|
||
if ($cnt_activ == 0 && ReadingsVal($name, 'internalTimer', 'stop') ne 'stop') { readingsBulkUpdate($hash, 'internalTimer' , 'stop'); }
|
||
if ($cnt_activ > 0) { readingsBulkUpdate($hash, 'internalTimer' , $intervall, 0); }
|
||
readingsEndUpdate($hash, 1);
|
||
|
||
return;
|
||
}
|
||
|
||
### list, Probably associated with ###
|
||
sub Timer_PawList {
|
||
my ($hash) = @_;
|
||
my $name = $hash->{NAME};
|
||
my $associatedWith = '';
|
||
|
||
foreach my $d (keys %{$hash->{READINGS}}) {
|
||
if ($d =~ /^Timer_(\d+)$/xms) {
|
||
my @values = split(',' , ReadingsVal($name, $d, ''));
|
||
### clear value, "Probably associated with" ne DEF
|
||
if ($values[7] ne 'DEF') {
|
||
if (not grep { /$values[6]/xms } $associatedWith) {
|
||
$associatedWith = $associatedWith eq '' ? $values[6] : $associatedWith.','.$values[6];
|
||
}
|
||
### Self-administration test, "Probably associated with" for DEF
|
||
} elsif ($values[7] eq 'DEF') {
|
||
my $Timer_set_attr = AttrVal($name, $d.'_set', '');
|
||
if ($Timer_set_attr ne '') {
|
||
Log3 $name, 5, "$name: PawList | look at DEF: ".$Timer_set_attr;
|
||
$Timer_set_attr =~ /(get|set)\s(\w+)\s/xms;
|
||
if ($2) {
|
||
Log3 $name, 5, "$name: PawList | found in DEF: ".$2;
|
||
if (not grep { /$2/xms } $associatedWith) {
|
||
$associatedWith = $associatedWith eq '' ? $2 : $associatedWith.','.$2;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
### END ###
|
||
}
|
||
}
|
||
|
||
Log3 $name, 5, "$name: PawList | Reading .associatedWith is: ".$associatedWith;
|
||
if ($associatedWith ne '') {
|
||
CommandSetReading(undef, "$name .associatedWith $associatedWith");
|
||
} else {
|
||
if(ReadingsVal($name, '.associatedWith', undef)) { readingsDelete($hash,'.associatedWith'); }
|
||
}
|
||
## current list "Probably associated with" finish ##
|
||
return;
|
||
}
|
||
|
||
##########################################
|
||
|
||
# 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
|
||
|
||
<a name="Timer"></a>
|
||
<h3>Timer Modul</h3>
|
||
<ul>
|
||
The timer module is a programmable timer with a maximum of 99 actions.<br><br>
|
||
In Frontend you can define new times and actions. The smallest possible definition of an action is a 10 second interval.<br>
|
||
You can use the dropdown menu to make the settings for the time switch point. Only after clicking on the <code> Save </code> 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.<br><br>
|
||
In addition, you can use the selection <code> SR </code> and <code> SS </code> in the hour and minute column. These rumps represent the time of sunrise and sunset.<br>
|
||
For example, if you select at minute <code> SS </code>, you have set the minutes of the sunset as the value. As soon as you set the value to <code> SS </code> at hour and minute
|
||
the timer uses the calculated sunset time at your location. <u><i>(For this calculation the FHEM global attributes latitude and longitude are necessary!)</u></i>
|
||
As soon as sunrise or sunset is selected for a timer, an additional column "Offset" is displayed. An offset in minutes in the range of -1440 to 1440 can be entered there.
|
||
|
||
<br><br>
|
||
<u>Programmable actions are currently:</u><br>
|
||
<ul>
|
||
<li><code> on | off</code> - The states must be supported by the device</li>
|
||
<li><code>DEF</code> - for a PERL code or a FHEM command <font color="red">*</font color> <br><br>
|
||
<ul><u>example for DEF:</u>
|
||
<li><code>{ Log 1, "Timer: now switch" }</code> (PERL code)</li>
|
||
<li><code>update</code> (FHEM command)</li>
|
||
<li><code>trigger Timer state:ins Log geschrieben</code> (FHEM-command)</li>
|
||
<li><code>set TYPE=IT:FILTER=room=Stube.*:FILTER=state=on off</code> (FHEM-command)</li></ul>
|
||
</li>
|
||
</ul>
|
||
<br>
|
||
|
||
<b><font color="red">*</font color></b> To do this, enter the code to be executed in the respective attribute. example: <code>Timer_03_set</code>
|
||
|
||
<br><br>
|
||
<u>Interval switching of the timer is only possible in the following variants:</u><br>
|
||
<ul>
|
||
<li>minute, define second and set all other values (minute, hour, day, month, year) to <code>all</code></li>
|
||
<li>hourly, define second + minute and set all other values (hour, day, month, year) to <code>all</code></li>
|
||
<li>daily, define second + minute + hour and set all other values (day, month, year) to <code>all</code></li>
|
||
<li>monthly, define second + minute + hour + day and set all other values (month, year) to <code>all</code></li>
|
||
<li>annually, define second + minute + hour + day + month and set the value (year) to <code>all</code></li>
|
||
<li>sunrise, define second & define minute + hour with <code>SR</code> and set all other values (day, month, year) to <code>all</code></li>
|
||
<li>sunset, define second & define minute + hour with <code>SS</code> and set all other values (day, month, year) to <code>all</code></li>
|
||
</ul>
|
||
<br>
|
||
Any interval circuits can be defined in which in the associated timer attribute e.g. the following Perl code is inserted:<br>
|
||
<code>{if ($min % 5 == 0) {fhem("set FS10_6_11 toggle");}}</code><br>
|
||
This timer would run every 5 minutes if the timer is configured to run in a minute as described.<br><br>
|
||
The following variables for time and date are available:<br>
|
||
<code>$sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst, $hms, $we, $today</code><br>
|
||
This makes it possible, for example, to have a timer run every Sunday at 15:30:00.
|
||
<br><br>
|
||
|
||
<b>Define</b><br>
|
||
<ul><code>define <NAME> Timer</code><br><br>
|
||
<u>example:</u>
|
||
<ul>
|
||
define timer Timer
|
||
</ul>
|
||
</ul><br>
|
||
|
||
<b>Set</b><br>
|
||
<ul>
|
||
<a name="addTimer"></a>
|
||
<li>addTimer: Adds a new timer</li><a name=""></a>
|
||
<a name="deleteTimer"></a>
|
||
<li>deleteTimer: Deletes the selected timer</li><a name=""></a>
|
||
<a name="offTimer"></a>
|
||
<li>offTimer: Individual timer is switched off.</li><a name=""></a>
|
||
<a name="offTimerAll"></a>
|
||
<li>offTimerAll: All timers are switched off.</li><a name=""></a>
|
||
<a name="onTimer"></a>
|
||
<li>onTimer: Individual timer is switched to active.</li><a name=""></a>
|
||
<a name="onTimerAll"></a>
|
||
<li>onTimerAll: All timers are activated.</li><a name=""></a>
|
||
<a name="saveTimers"></a>
|
||
<li>saveTimers: Saves the settings in file <code>Timers.txt</code> on directory <code>./FHEM/lib</code>.</li>
|
||
<a name="sortTimer"></a>
|
||
<li>sortTimer: Sorts the saved timers alphabetically.</li>
|
||
</ul><br><br>
|
||
|
||
<b>Get</b><br>
|
||
<ul>
|
||
<a name="loadTimers"></a>
|
||
<li>loadTimers: Loads a saved configuration from file <code>Timers.txt</code> from directory <code>./FHEM/lib</code>.</li><a name=""></a>
|
||
</ul><br><br>
|
||
|
||
<b>Attribute</b><br>
|
||
<ul><li><a href="#disable">disable</a></li></ul><br>
|
||
<ul><li><a name="stateFormat">stateFormat</a><br>
|
||
It is used to format the value <code>state</code><br>
|
||
<u>example:</u> <code>{ ReadingsTimestamp($name, "state", 0) ."&nbsp;- ". ReadingsVal($name, "state", "none");}</code><br>
|
||
- will format to output: <code>2019-09-19 17:51:44 - Timer_04 saved</code></li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Border">Table_Border</a><br>
|
||
Shows the table border. (on | off = default)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Border_Cell">Table_Border_Cell</a><br>
|
||
Shows the cell frame. (on | off = default)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Header_with_time">Table_Header_with_time</a><br>
|
||
Shows or hides the sunrise and sunset with the local time above the table. (on | off, standard off)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Size_TextBox">Table_Size_TextBox</a><br>
|
||
Correction value to change the length of the text box for the device name / designation. (default 90)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Style">Table_Style</a><br>
|
||
Turns on the defined table style. (on | off, default off)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_View_in_room">Table_View_in_room</a><br>
|
||
Toggles the tables UI in the room view on or off. (on | off, standard on)<br>
|
||
<small><i>In the room <code> Unsorted </code> the table UI is always switched off!</i></small></li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Timer_preselection">Timer_preselection</a><br>
|
||
Sets the input values for a new timer to the current time. (on | off = default)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Timer_xx_set">Timer_xx_set</a><br>
|
||
Location for the FHEM command or PERL code of the timer (xx has the numerical value of 01-99). WITHOUT this attribute, which <b> only appears if action <code> DEF </code> is set </b>,
|
||
The module does not process FHEM command or PERL code from the user. <code><font color="red">*</font color> </code></li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Offset_Horizon">Offset_Horizon</a><br>
|
||
Different elevation angles are used to calculate sunrise and sunset times.<br>
|
||
(HORIZON = -0.833°REAL = 0°, CIVIL = -6°, NAUTIC = -12°, ASTRONOMIC = -18°, default REAL)<br>
|
||
Most sites on the Internet prefer an offset of -0.833°.
|
||
</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Show_DeviceInfo">Show_DeviceInfo</a><br>
|
||
Shows the additional information (alias | comment, default off)</li><a name=" "></a></ul><br>
|
||
<br>
|
||
|
||
<b><i>Generated readings</i></b><br>
|
||
<ul><li>Timer_xx<br>
|
||
Memory values of the individual timer</li><br>
|
||
<li>internalTimer<br>
|
||
State of the internal timer (stop or Interval until the next call)</li><br><br></ul>
|
||
|
||
<b><i><u>Hints:</u></i></b><br>
|
||
<ul><li>Entries in the system logfile like: <code>2019.09.20 22:15:01 3: Timer: time difference too large! interval=59, Sekunde=01</code> say that the timer has recalculated the time.</li></ul>
|
||
<ul><li>The offset function can only be activated at sunrise (SR) and sunset (SS).</li></ul>
|
||
<ul><li>To implement a switching group, you can combine the timer module with the structure module.</li></ul>
|
||
|
||
</ul>
|
||
=end html
|
||
|
||
|
||
=begin html_DE
|
||
|
||
<a name="Timer"></a>
|
||
<h3>Timer Modul</h3>
|
||
<ul>
|
||
Das Timer Modul ist eine programmierbare Schaltuhr mit maximal 99 Aktionen.<br><br>
|
||
Im Frontend können Sie neue Zeitpunkte und Aktionen definieren. Die kleinstmögliche Definition einer Aktion ist ein 10 Sekunden Intervall.<br>
|
||
Mittels der Dropdown Menüs können Sie die Einstellungen für den Zeitschaltpunkt vornehmen. Erst nach dem drücken auf den <code>Speichern</code> Knopf wird die Einstellung übernommen.<br><br>
|
||
In der DropDown-Liste stehen jeweils die Zahlenwerte für Jahr / Monat / Tag / Stunde / Minute / Sekunde zur Auswahl.<br>
|
||
Zusätzlich können Sie in der Spalte Stunde und Minute die Auswahl <code>SA</code> und <code>SU</code> nutzen. Diese Kürzel stehen für den Zeitpunkt Sonnenaufgang und Sonnenuntergang.<br>
|
||
Wenn sie Beispielsweise bei Minute <code>SU</code> auswählen, so haben Sie die Minuten des Sonnenuntergang als Wert gesetzt. Sobald Sie bei Stunde und Minute den Wert auf <code>SU</code>
|
||
stellen, so nutzt der Timer den errechnenten Zeitpunkt Sonnenuntergang an Ihrem Standort. <u><i>(Für diese Berechnung sind die FHEM globalen Attribute latitude und longitude notwendig!)</u></i>
|
||
Sobald bei einem Timer Sonnenaufgang oder Sonnenuntergang ausgewählt ist, wird eine zusätzliche Spalte "Offset" angezeigt. Dort kann ein Offset in Minuten im Bereich von -1440 bis 1440 eingetragen werden.
|
||
|
||
<br><br>
|
||
<u>Programmierbare Aktionen sind derzeit:</u><br>
|
||
<ul>
|
||
<li><code> on | off</code> - Die Zustände müssen von dem zu schaltenden Device unterstützt werden</li>
|
||
<li><code>DEF</code> - für einen PERL-Code oder ein FHEM Kommando <font color="red">*</font color> <br><br>
|
||
<ul><u>Beispiele für DEF:</u>
|
||
<li><code>{ Log 1, "Timer: schaltet jetzt" }</code> (PERL-Code)</li>
|
||
<li><code>update</code> (FHEM-Kommando)</li>
|
||
<li><code>trigger Timer state:ins Log geschrieben</code> (FHEM-Kommando)</li>
|
||
<li><code>set TYPE=IT:FILTER=room=Stube.*:FILTER=state=on off</code> (FHEM-Kommando)</li></ul>
|
||
</li>
|
||
</ul>
|
||
<br>
|
||
|
||
<b><font color="red">*</font color></b> Hierfür hinterlegen Sie den auszuführenden PERL-Code in das jeweilige Attribut. Bsp.: <code>Timer_03_set</code>
|
||
|
||
<br><br>
|
||
<u>Eine Intervallschaltung des Timer ist nur möglich in folgenden Varianten:</u><br>
|
||
<ul>
|
||
<li>minütlich, Sekunde definieren und alle anderen Werte (Minute, Stunde, Tag, Monat, Jahr) auf <code>alle</code> setzen</li>
|
||
<li>stündlich, Sekunde + Minute definieren und alle anderen Werte (Stunde, Tag, Monat, Jahr) auf <code>alle</code> setzen</li>
|
||
<li>täglich, Sekunde + Minute + Stunde definieren und alle anderen Werte (Tag, Monat, Jahr) auf <code>alle</code> setzen</li>
|
||
<li>monatlich, Sekunde + Minute + Stunde + Tag definieren und alle anderen Werte (Monat, Jahr) auf <code>alle</code> setzen</li>
|
||
<li>jährlich, Sekunde + Minute + Stunde + Tag + Monat definieren und den Wert (Jahr) auf <code>alle</code> setzen</li>
|
||
<li>Sonnenaufgang, Sekunde definieren & Minute + Stunde definieren mit <code>SA</code> und alle anderen Werte (Tag, Monat, Jahr) auf <code>alle</code> setzen</li>
|
||
<li>Sonnenuntergang, Sekunde definieren & Minute + Stunde definieren mit <code>SU</code> und alle anderen Werte (Tag, Monat, Jahr) auf <code>alle</code> setzen</li>
|
||
</ul>
|
||
<br>
|
||
Beliebige Intervallschaltungen können definiert werden, in dem im zugehörigen Timer-Attribut z.B. folgender Perl-Code eingefügt wird:<br>
|
||
<code>{if ($min % 5 == 0) {fhem("set FS10_6_11 toggle");}}</code><br>
|
||
Dieser Timer würde dann aller 5 Minuten ausgeführt, wenn der Timer wie beschrieben auf minütliches Ausführen konfiguriert ist.<br><br>
|
||
Folgende Variablen für Zeit- und Datumsangaben stehen zur Verfügung:<br>
|
||
<code>$sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst, $hms, $we, $today</code><br>
|
||
Damit ist es möglich, einen Timer beispielsweise nur jeden Sonntag um 15:30:00 Uhr etwas ausführen zu lassen.
|
||
<br><br>
|
||
|
||
<b>Define</b><br>
|
||
<ul><code>define <NAME> Timer</code><br><br>
|
||
<u>Beispiel:</u>
|
||
<ul>
|
||
define Schaltuhr Timer
|
||
</ul>
|
||
</ul><br>
|
||
|
||
<b>Set</b><br>
|
||
<ul>
|
||
<a name="addTimer"></a>
|
||
<li>addTimer: Fügt einen neuen Timer hinzu.</li><a name=""></a>
|
||
<a name="deleteTimer"></a>
|
||
<li>deleteTimer: Löscht den ausgewählten Timer.</li><a name=""></a>
|
||
<a name="offTimer"></a>
|
||
<li>offTimer: Einzelner Timer wird abgeschalten.</li><a name=""></a>
|
||
<a name="offTimerAll"></a>
|
||
<li>offTimerAll: Alle Timer werden abgeschalten.</li><a name=""></a>
|
||
<a name="onTimer"></a>
|
||
<li>onTimer: Einzelner Timer wird aktiv geschalten.</li><a name=""></a>
|
||
<a name="onTimerAll"></a>
|
||
<li>onTimerAll: Alle Timer werden aktiv geschalten.</li><a name=""></a>
|
||
<a name="saveTimers"></a>
|
||
<li>saveTimers: Speichert die eingestellten Timer in der Datei <code>Timers.txt</code> im Verzeichnis <code>./FHEM/lib</code>. </li>
|
||
<a name="sortTimer"></a>
|
||
<li>sortTimer: Sortiert die gespeicherten Timer alphabetisch.</li>
|
||
</ul><br><br>
|
||
|
||
<b>Get</b><br>
|
||
<ul>
|
||
<a name="loadTimers"></a>
|
||
<li>loadTimers: Läd eine gespeicherte Konfiguration aus der Datei <code>Timers.txt</code> aus dem Verzeichnis <code>./FHEM/lib</code>.</li><a name=""></a>
|
||
</ul><br><br>
|
||
|
||
<b>Attribute</b><br>
|
||
<ul><li><a href="#disable">disable</a></li></ul><br>
|
||
<ul><li><a name="stateFormat">stateFormat</a><br>
|
||
Es dient zur Formatierung des Wertes <code>state</code><br>
|
||
<u>Beispiel:</u> <code>{ ReadingsTimestamp($name, "state", 0) ."&nbsp;- ". ReadingsVal($name, "state", "none");}</code><br>
|
||
- wird zur formatieren Ausgabe: <code>2019-09-19 17:51:44 - Timer_04 saved</code></li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Border">Table_Border</a><br>
|
||
Blendet den Tabellenrahmen ein. (on | off, standard off)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Border_Cell">Table_Border_Cell</a><br>
|
||
Blendet den Cellrahmen ein. (on | off, standard off)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Header_with_time">Table_Header_with_time</a><br>
|
||
Blendet den Sonnenauf und Sonnenuntergang mit der lokalen Zeit über der Tabelle ein oder aus. (on | off, standard off)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Size_TextBox">Table_Size_TextBox</a><br>
|
||
Korrekturwert um die Länge der Textbox für die Gerätenamen / Bezeichung zu verändern. (standard 90)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_Style">Table_Style</a><br>
|
||
Schaltet den definierten Tabellen-Style ein. (on | off, standard off)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Table_View_in_room">Table_View_in_room</a><br>
|
||
Schaltet das Tabellen UI in der Raumansicht an oder aus. (on | off, standard on)<br>
|
||
<small><i>Im Raum <code>Unsorted</code> ist das Tabellen UI immer abgeschalten!</i></small></li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Timer_preselection">Timer_preselection</a><br>
|
||
Setzt die Eingabewerte bei einem neuen Timer auf die aktuelle Zeit. (on | off, standard off)</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Timer_xx_set">Timer_xx_set</a><br>
|
||
Speicherort für das FHEM-Kommando oder den PERL-Code des Timers (xx hat den Zahlenwert von 01-99). OHNE dieses Attribut, welches <b>nur erscheint wenn die Aktion <code> DEF </code> eingestellt </b> ist,
|
||
verarbeitet das Modul kein Kommando oder PERL-Code vom Benutzer. <code> <font color="red">*</font color> </code></li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Offset_Horizon">Offset_Horizon</a><br>
|
||
Für die Berechnung der Zeiten von Sonnenaufgang und Sonnenuntergang werden verschiedene Höhenwinkel verwendet.<br>
|
||
(HORIZON = -0.833°, REAL = 0°, CIVIL = -6°, NAUTIC = -12°, ASTRONOMIC = -18°, Standard REAL)<br>
|
||
Die meisten Seiten im Internet bevorzugen einen Offset von -0.833°.
|
||
</li><a name=" "></a></ul><br>
|
||
<ul><li><a name="Show_DeviceInfo">Show_DeviceInfo</a><br>
|
||
Blendet die Zusatzinformation ein. (alias | comment, standard off)</li><a name=" "></a></ul><br>
|
||
<br>
|
||
|
||
<b><i>Generierte Readings</i></b><br>
|
||
<ul><li>Timer_xx<br>
|
||
Speicherwerte des einzelnen Timers</li><br>
|
||
<li>internalTimer<br>
|
||
Zustand des internen Timers (stop oder Intervall bis zum nächsten Aufruf)</li><br><br></ul>
|
||
|
||
<b><i><u>Hinweise:</u></i></b><br>
|
||
<ul><li>Einträge im Systemlogfile wie: <code>2019.09.20 22:15:01 3: Timer: time difference too large! interval=59, Sekunde=01</code> sagen aus, das der Timer die Zeit neu berechnet hat.</li></ul>
|
||
<ul><li>Die Funktion Offset ist nur bei Sonnenaufgang (SA) und Sonnenuntergang (SU) aktivierbar.</li></ul>
|
||
<ul><li>Um eine Gruppenschaltung umzusetzen, so kann man das Timer Modul mit dem structure Modul kombinieren.</li></ul>
|
||
</ul>
|
||
=end html_DE
|
||
|
||
=for :application/json;q=META.json 88_Timer.pm
|
||
{
|
||
"author": [
|
||
"HomeAuto_User <>",
|
||
"elektron-bbs"
|
||
],
|
||
"description": "timer for executing actions with only one InternalTimer",
|
||
"dynamic_config": 1,
|
||
"keywords": [
|
||
"Timer",
|
||
"fhem-sonstige-systeme"
|
||
],
|
||
"license": [
|
||
"GPL_2"
|
||
],
|
||
"meta-spec": {
|
||
"url": "https://metacpan.org/pod/CPAN::Meta::Spec",
|
||
"version": 2
|
||
},
|
||
"name": "FHEM::Timer",
|
||
"prereqs": {
|
||
"runtime": {
|
||
"requires": {
|
||
"FHEM": 5.00918623,
|
||
"FHEM::Meta": 0.001006,
|
||
"JSON::PP": 0,
|
||
"Time::HiRes": 0,
|
||
"lib::SD_Protocols": "0",
|
||
"perl": 5.018,
|
||
"strict": "0",
|
||
"warnings": "0"
|
||
}
|
||
},
|
||
"develop": {
|
||
"requires": {
|
||
"lib::SD_Protocols": "0",
|
||
"strict": "0",
|
||
"warnings": "0"
|
||
}
|
||
}
|
||
},
|
||
"version": "v25.01.30",
|
||
"release_status": "stable",
|
||
"resources": {
|
||
"bugtracker": {
|
||
"web": "https://github.com/fhem/Timer/issues"
|
||
},
|
||
"repository": {
|
||
"x_master": {
|
||
"type": "git",
|
||
"url": "https://github.com/fhem/Timer",
|
||
"web": "https://github.com/fhem/Timer/blob/master/FHEM/88_Timer.pm"
|
||
},
|
||
"type": "",
|
||
"url": "https://github.com/fhem/Timer",
|
||
"web": "https://github.com/fhem/Timer/blob/master/FHEM/88_Timer.pm",
|
||
"x_branch": "master",
|
||
"x_filepath": "FHEM/",
|
||
"x_raw": "https://github.com/fhem/Timer/blob/master/FHEM/88_Timer.pm",
|
||
"x_dev": {
|
||
"type": "git",
|
||
"url": "https://github.com/fhem/Timer.git",
|
||
"web": "https://github.com/fhem/Timer/blob/pre-release/FHEM/88_Timer.pm",
|
||
"x_branch": "pre-release",
|
||
"x_filepath": "FHEM/",
|
||
"x_raw": "https://raw.githubusercontent.com/fhem/Timer/pre-release/FHEM/88_Timer.pm"
|
||
}
|
||
},
|
||
"x_commandref": {
|
||
"web": "https://commandref.fhem.de/#Timer"
|
||
},
|
||
"x_wiki": {
|
||
"web": "https://wiki.fhem.de/wiki/Timer"
|
||
}
|
||
},
|
||
"x_fhem_maintainer": [
|
||
"HomeAuto_User",
|
||
"elektron-bbs"
|
||
],
|
||
"x_fhem_maintainer_github": [
|
||
"HomeAutoUser",
|
||
"elektron-bbs"
|
||
]
|
||
}
|
||
=end :application/json;q=META.json
|
||
|
||
=cut |