diff --git a/fhem/FHEM/95_YAAHM.pm b/fhem/FHEM/95_YAAHM.pm new file mode 100644 index 000000000..2ae8e8a01 --- /dev/null +++ b/fhem/FHEM/95_YAAHM.pm @@ -0,0 +1,3018 @@ +######################################################################################## +# +# YAAHM.pm +# +# Yet Another Auto Home Module for FHEM +# +# Prof. Dr. Peter A. Henning +# +# $Id$ +# +######################################################################################## +# +# This programm is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +######################################################################################## + +package main; + +use strict; +use warnings; +use vars qw(%defs); # FHEM device/button definitions +use vars qw(%intAt); # FHEM at definitions +use vars qw($FW_ME); +use vars qw($FW_inform); +use vars qw($FW_headerlines); +use vars qw($FW_id); + +use Data::Dumper; +use Math::Trig; +use JSON; # imports encode_json, decode_json, to_json and from_json. + +######################### +# Global variables +my $yaahmname; +my $yaahmlinkname = "Profile"; # link text +my $yaahmhiddenroom = "ProfileRoom"; # hidden room +my $yaahmpublicroom = "Unsorted"; # public room +my $yaahmversion = "1.0"; +my $firstcall=1; + +my %yaahm_transtable_EN = ( + "ok" => "OK", + "notok" => "Not OK", + "start" => "Start", + "status" => "Status", + "notstarted" => "Not started", + "next" => "Next", + "manual" => "Manual", + "active" => "Active", + "inactive" => "Inactive", + "overview" => "Summary", + "name" => "Name", + "event" => "Event", + "time" => "Time", + "timer" => "Timer", + "action" => "Action", + "weekly" => "Weekly ", + "day" => "Day", + "daytime" => "Daytime", + "nighttime" => "Nighttime", + "daylight" => "Daylight", + "daytype" => "Day Type", + "daily" => "Daily ", + "type" => "Type", + "description" => "Description", + "profile" => "Profile", + "profiles" => "Profiles", + #-- + "aftermidnight" => "After Midnight", + "beforesunrise" => "Before Sunrise", + "sunrise" => "Sunrise", + "aftersunrise" => "After Sunrise", + "wakeup" => "WakeUp", + "morning" => "Morning", + "noon" => "Noon", + "afternoon" => "Afternoon", + "evening" => "Evening", + "beforesunset" => "Before Sunset", + "sunset" => "Sunset", + "aftersunset" => "After Sunset", + "sleep" => "Sleep", + "night" => "Night", + "beforemidnight" => "Before Midnight", + #-- + "date" => "Date", + "today" => "Today", + "tomorrow" => "Tomorrow", + "workday" => "Workday", + "weekend" => "Weekend", + "vacation" => "Vacation", + "holiday" => "Holiday", + "weekday" => "Day of Week", + #-- + "mode" => "Mode", + "normal" => "Normal", + "party" => "Party", + "absence" => "Absence", + #-- + "state" => "Security", + "secstate" => "Device states", + "unlocked" => "Unlocked", + "locked" => "Locked", + "unsecured" => "Not Secured", + "secured" => "Secured", + "protected" => "Geschützt", + "guarded" => "Guarded", + #-- + "monday" => ["Monday","Mon"], + "tuesday" => ["Tuesday","Tue"], + "wednesday" => ["Wednesday","Wed"], + "thursday" => ["Thursday","Thu"], + "friday" => ["Friday","Fri"], + "saturday" => ["Saturday","Sat"], + "sunday" => ["Sunday","Sun"], + #-- + "spring" => "Spring", + "summer" => "Summer", + "fall" => "Fall", + "winter" => "Winter" + ); + + my %yaahm_transtable_DE = ( + "ok" => "OK", + "notok" => "Nicht OK", + "start" => "Start", + "status" => "Status", + "notstarted" => "Nicht gestartet", + "next" => "Nächste", + "manual" => "Manuell", + "active" => "Aktiv", + "inactive" => "Inaktiv", + "overview" => "Zusammenfassung", + "name" => "Name", + "event" => "Event", + "time" => "Zeit", + "timer" => "Timer", + "action" => "Aktion", + "weekly" => "Wochen-", + "day" => "Tag", + "daytime" => "Tageszeit", + "nighttime" => "Nachtzeit", + "daylight" => "Tageslicht", + "daytype" => "Tagestyp", + "daily" => "Tages-", + "type" => "Typ", + "description" => "Beschreibung", + "profile" => "Profil", + "profiles" => "Profile", + #-- + "aftermidnight" => "Nach Mitternacht", + "beforesunrise" => "Vor Sonnenaufgang", + "sunrise" => "Sonnenaufgang", + "aftersunrise" => "Nach Sonnenaufgang", + "wakeup" => "Wecken", + "morning" => "Morgen", + "noon" => "Mittag", + "afternoon" => "Nachmittag", + "evening" => "Abend", + "beforesunset" => "Vor Sonnenuntergang", + "sunset" => "Sonnenuntergang", + "aftersunset" => "Nach Sonnenuntergang", + "sleep" => "Schlafen", + "night" => "Nacht", + "beforemidnight" => "Vor Mitternacht", + #-- + "date" => "Termin", + "today" => "Heute", + "tomorrow" => "Morgen", + "workday" => "Arbeitstag", + "weekend" => "Wochenende", + "vacation" => "Ferientag", + "holiday" => "Feiertag", + "weekday" => "Wochentag", + #-- + "mode" => "Modus", + "normal" => "Normal", + "party" => "Party", + "absence" => "Abwesenheit", + #-- + "state" => "Sicherheit", + "secstate" => "Device States", + "unlocked" => "Unverschlossen", + "locked" => "Verschlossen", + "unsecured" => "Nicht Gesichert", + "secured" => "Gesichert", + "protected" => "Geschützt", + "guarded" => "Überwacht", + #-- + "monday" => ["Montag","Mo"], + "tuesday" => ["Dienstag","Di"], + "wednesday" => ["Mittwoch","Mi"], + "thursday" => ["Donnerstag","Do"], + "friday" => ["Freitag","Fr"], + "saturday" => ["Samstag","Sa"], + "sunday" => ["Sonntag","So"], + #-- + "spring" => "Frühling", + "summer" => "Sommer", + "fall" => "Herbst", + "winter" => "Winter" + ); + +my $yaahm_tt; + +#-- default values, need to be overwritten from save file +# first and second parameter +# entries in the default table with no time entry are single-timers +# entries in the default table with only first time are single-timers +# entries in the default table with only second time are single-timer offsets +# entries in the default table with first an second time are two-timer periods +# third parameter +# fourth parameter + +my %defaultdailytable = ( + "aftermidnight" => [undef,"00:01",undef,undef], + "beforesunrise" => [undef,"01:00",undef,undef], + "sunrise" => [undef,undef,undef,undef], + "aftersunrise" => [undef,"01:00",undef,undef], + "wakeup" => ["06:15",undef,undef,undef], + "morning" => ["08:00",undef,undef,undef], + "noon" => ["13:00",undef,undef,undef], + "afternoon" => ["14:00",undef,undef,undef], + "evening" => ["18:30",undef,undef,undef], + "beforesunset" => [undef,"01:00",undef,undef], + "sunset" => [undef,undef,undef,undef], + "aftersunset" => [undef,"01:00",undef,undef], + "sleep" => ["22:30",undef,undef,undef], + "night" => ["22:00",undef,undef,undef], + "beforemidnight" => [undef,"00:05",undef,undef]); + +my %dailytable = (); + +sub YAAHM_dsort { + $dailytable{$a}[0] cmp $dailytable{$b}[0] +} + +my @weeklytable = ( + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday"); + +my %defaultwakeuptable = ( + "name" => "", + "action" => "", + "monday" => "06:15", + "tuesday" => "06:15", + "wednesday" => "06:15", + "thursday" => "06:15", + "friday" => "06:15", + "saturday" => "off", + "sunday" => "off"); + +my %defaultsleeptable = ( + "name" => "", + "action" => "", + "monday" => "22:30", + "tuesday" => "22:30", + "wednesday" => "22:30", + "thursday" => "22:30", + "friday" => "23:00", + "saturday" => "23:00", + "sunday" => "22:30"); + +my @daytype = ( + "workday", + "vacation", + "weekend", + "holiday"); + +my %defaultdayproperties = ( + "date" => "", + "weekday" => "", + "daytype" => 0, + "desc" => "", + "season" => ""); + +my @times = (keys %defaultdailytable); + +my @modes = ( + "normal","party","absence"); + +my @states = ( + "unsecured","secured","protected","guarded"); + + +my @seasons = ( + "winter","spring","summer","fall"); + +#-- modes or day types that affect the profile +my @profmode = ("party","absence"); +my @profday = ("vacation","holiday"); + +######################################################################################### +# +# YAAHM_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "YAAHM_Define"; + $hash->{SetFn} = "YAAHM_Set"; + $hash->{GetFn} = "YAAHM_Get"; + $hash->{UndefFn} = "YAAHM_Undef"; + $hash->{AttrFn} = "YAAHM_Attr"; + my $attst = "linkname publicroom hiddenroom lockstate:locked,unlocked simulation:0,1 ". + "timeHelper modeHelper modeAuto:0,1 stateDevices:textField-long stateInterval stateWarning stateHelper stateAuto:0,1 ". + "holidayDevices:textField-long vacationDevices:textField-long specialDevices:textField-long"; + + $hash->{AttrList} = $attst; + + if( !defined($yaahm_tt) ){ + #-- in any attribute redefinition readjust language + my $lang = AttrVal("global","language","EN"); + if( $lang eq "DE"){ + $yaahm_tt = \%yaahm_transtable_DE; + }else{ + $yaahm_tt = \%yaahm_transtable_EN; + } + } + $yaahmlinkname = $yaahm_tt->{"profiles"}; + + $data{FWEXT}{YAAHMx}{LINK} = "?room=".$yaahmhiddenroom; + $data{FWEXT}{YAAHMx}{NAME} = $yaahmlinkname; + + $data{FWEXT}{"/YAAHM_timewidget"}{FUNC} = "YAAHM_timewidget"; + $data{FWEXT}{"/YAAHM_timewidget"}{FORKABLE} = 0; + + return undef; +} + +######################################################################################### +# +# YAAHM_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################### + +sub YAAHM_Define ($$) { + my ($hash, $def) = @_; + my $now = time(); + my $name = $hash->{NAME}; + my $TYPE = $hash->{TYPE}; + + $hash->{VERSION} = $yaahmversion; + $yaahmname = $name; + + #-- readjust language + my $lang = AttrVal("global","language","EN"); + if( $lang eq "DE"){ + $yaahm_tt = \%yaahm_transtable_DE; + }else{ + $yaahm_tt = \%yaahm_transtable_EN; + } + #$hash->{DATA}{"TT"}=$yaahm_tt; + + # NOTIFYDEV + my $NOTIFYDEV = "global,$name"; + unless ( defined( $hash->{NOTIFYDEV} ) && $hash->{NOTIFYDEV} eq $NOTIFYDEV ) + { + $hash->{NOTIFYDEV} = $NOTIFYDEV; + #$changed = 1; + } + + readingsSingleUpdate( $hash, "state", "Initialized", 1 ); + + $yaahmlinkname = defined($attr{$name}{"linkname"}) ? $attr{$name}{"linkname"} : $yaahmlinkname; + $yaahmhiddenroom = defined($attr{$name}{"hiddenroom"}) ? $attr{$name}{"hiddenroom"} : $yaahmhiddenroom; + $data{FWEXT}{YAAHMx}{LINK} = "?room=".$yaahmhiddenroom; + $data{FWEXT}{YAAHMx}{NAME} = $yaahmlinkname; + $attr{$name}{"room"} = $yaahmhiddenroom; + + #-- clone daily default profile + $hash->{DATA}{"DT"} = {%defaultdailytable}; + #-- clone weekly default profile + $hash->{DATA}{"WT"} = (); + push(@{$hash->{DATA}{"WT"}},{%defaultwakeuptable}); + $hash->{DATA}{"WT"}[0]{"name"} = $yaahm_tt->{"wakeup"}; + push(@{$hash->{DATA}{"WT"}},{%defaultsleeptable}); + $hash->{DATA}{"WT"}[1]{"name"} = $yaahm_tt->{"sleep"}; + + #-- clone days for today and tomorrow + $hash->{DATA}{"DD"} = (); + push(@{$hash->{DATA}{"DD"}},{%defaultdayproperties}); + push(@{$hash->{DATA}{"DD"}},{%defaultdayproperties}); + + #-- determine Astro device + if( !exists($modules{Astro}{defptr}) ){ + Log3 $name,1,"[YAAHM] does not find an Astro device, loading module Astro separately"; + require "95_Astro.pm"; + }else{ + my @keys = sort keys %{$modules{Astro}{defptr}}; + Log3 $name,1,"[YAAHM] finds ".int(@keys)." Astro devices, module not loaded separately"; + } + + #-- + $modules{YAAHM}{defptr}{$name} = $hash; + + RemoveInternalTimer($hash); + InternalTimer ($now + 5, 'YAAHM_CreateEntry', $hash, 0); + + return; +} + +######################################################################################### +# +# YAAHM_Undef - Implements Undef function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################### + +sub YAAHM_Undef ($$) { + my ($hash,$arg) = @_; + + my $name = $hash->{NAME}; + + RemoveInternalTimer($hash); + delete $data{FWEXT}{YAAHMx}; + if (defined $defs{$name."_weblink"}) { + FW_fC("delete ".$name."_weblink"); + Log3 $hash, 3, "[".$name. " V".$yaahmversion."]"." Weblink ".$name."_weblink deleted"; + } + if (defined $defs{$name."_shortlink"}) { + FW_fC("delete ".$name."_shortlink"); + Log3 $hash, 3, "[".$name. " V".$yaahmversion."]"." Weblink ".$name."_shortlink deleted"; + } + delete($modules{YAAHM}{defptr}); + return undef; +} + +######################################################################################### +# +# YAAHM_Attr - Implements Attr function +# +# Parameter hash = hash of device addressed, ??? +# +######################################################################################### + +sub YAAHM_Attr($$$) { + my ($cmd, $name, $attrName, $attrVal) = @_; + + my $hash = $defs{"$name"}; + + #-- in any attribute redefinition readjust language + my $lang = AttrVal("global","language","EN"); + if( $lang eq "DE"){ + $yaahm_tt = \%yaahm_transtable_DE; + }else{ + $yaahm_tt = \%yaahm_transtable_EN; + } + + #--------------------------------------- + if ( $attrName eq "timeHelper" ) { + my $dh = (defined($attr{$name}{"timeHelper"})) ? $attr{$name}{"timeHelper"} : undef; + #-- remove this function from all entries + if( $cmd eq "del" ){ + foreach my $key (keys %defaultdailytable){ + my $xval = $hash->{DATA}{"DT"}{$key}[2]; + if( $xval =~ /^{$dh/){ + my @cmds = split(',',$xval); + shift(@cmds); + $xval = join(',',@cmds); + $hash->{DATA}{"DT"}{$key}[2] = $xval; + } + } + } + #--------------------------------------- + }elsif ( ($cmd eq "set") && ($attrName eq "linkname") ) { + + $yaahmlinkname = $attrVal; + $data{FWEXT}{YAAHMx}{NAME} = $yaahmlinkname; + + #--------------------------------------- + }elsif ( ($cmd eq "set") && ($attrName eq "publicroom") ) { + + $yaahmpublicroom = $attrVal; + FW_fC("attr ".$name."_shortlink room ".$yaahmpublicroom); + + #--------------------------------------- + }elsif ( ($cmd eq "set") && ($attrName eq "hiddenroom") ){ + #-- remove old hiddenroom from FHEMWEB instances + foreach my $dn (sort keys %defs) { + if ($defs{$dn}{TYPE} eq "FHEMWEB" && $defs{$dn}{NAME} !~ /FHEMWEB:/) { + my $hr = AttrVal($defs{$dn}{NAME}, "hiddenroom", ""); + $hr =~ s/$yaahmhiddenroom//; + $hr =~ s/,,//; + $hr =~ s/,$//; + FW_fC("attr ".$defs{$dn}{NAME}." hiddenroom ".$hr); + } + } + + #-- new value + $yaahmhiddenroom = $attrVal; + $data{FWEXT}{YAAHMx}{LINK} = "?room=".$yaahmhiddenroom; + FW_fC("attr ".$name."_weblink room ".$yaahmhiddenroom); + #-- place into FHEMWEB instances + foreach my $dn (sort keys %defs) { + if ($defs{$dn}{TYPE} eq "FHEMWEB" && $defs{$dn}{NAME} !~ /FHEMWEB:/) { + my $hr = AttrVal($defs{$dn}{NAME}, "hiddenroom", ""); + if (index($hr,$yaahmhiddenroom) == -1){ + if ($hr eq "") { + FW_fC("attr ".$defs{$dn}{NAME}." hiddenroom ".$yaahmhiddenroom); + }else { + FW_fC("attr ".$defs{$dn}{NAME}." hiddenroom ".$hr.",".$yaahmhiddenroom); + } + Log3 $hash, 3, "[".$name. " V".$yaahmversion."]"." Added hidden room '".$yaahmhiddenroom."' to ".$defs{$dn}{NAME}; + } + } + } + #--------------------------------------- + }elsif ( ($cmd eq "delete") && ($attrName eq "stateDevices") ) { + fhem("deletereading $name sdev_housestate"); + fhem("deletereading $name sec_housestate"); + fhem("deletereading $name sym_housestate"); + YAAHM_RemoveInternalTimer("check",$hash); + + #--------------------------------------- + }elsif ( ($cmd eq "set") && ($attrName eq "stateInterval") ) { + my $next = gettimeofday()+AttrVal($name,"stateInterval",60)*60; + YAAHM_RemoveInternalTimer("check",$hash); + YAAHM_InternalTimer("check",$next, "YAAHM_checkstate", $hash, 0); + + #--------------------------------------- + }elsif ( ($cmd eq "delete") && ($attrName eq "stateInterval") ) { + my $next = gettimeofday()+3600; + YAAHM_RemoveInternalTimer("check",$hash); + YAAHM_InternalTimer("check",$next, "YAAHM_checkstate", $hash, 0); + + #--------------------------------------- + }elsif ( $attrName eq "holidayDevices" ) { + return "Value for $attrName has invalid format" + unless ( $cmd eq "del" || $attrVal =~ m/^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ ); + #--------------------------------------- + }elsif ( $attrName eq "vacationDevices" ) { + return "Value for $attrName has invalid format" + unless ( $cmd eq "del" || $attrVal =~ m/^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ ); + } + + return; +} + +######################################################################################### +# +# YAAHM_CreateEntry - Puts the YAAHM entry into the FHEM menu +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_CreateEntry($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + + $yaahmlinkname = defined($attr{$name}{"linkname"}) ? $attr{$name}{"linkname"} : $yaahmlinkname; + $yaahmpublicroom = defined($attr{$name}{"publicroom"}) ? $attr{$name}{"publicroom"} : $yaahmpublicroom; + $yaahmhiddenroom = defined($attr{$name}{"hiddenroom"}) ? $attr{$name}{"hiddenroom"} : $yaahmhiddenroom; + + #-- this is the long YAAHM entry + FW_fC("defmod ".$name."_weblink weblink htmlCode {YAAHM_Longtable(\"".$name."\")}"); + Log3 $hash, 3, "[".$name. " V".$yaahmversion."]"." Weblink ".$name."_weblink created"; + FW_fC("attr ".$name."_weblink room ".$yaahmhiddenroom) + if(!defined($attr{$name."_weblink"}{"room"})); + + #-- this is the short YAAHM entry + FW_fC("defmod ".$name."_shortlink weblink htmlCode {YAAHM_Shorttable(\"".$name."\")}"); + Log3 $hash, 3, "[".$name. " V".$yaahmversion."]"." Weblink ".$name."_shortlink created"; + FW_fC("attr ".$name."_shortlink room ".$yaahmpublicroom) + if(!defined($attr{$name."_shortlink"}{"room"})); + + foreach my $dn (sort keys %defs) { + if ($defs{$dn}{TYPE} eq "FHEMWEB" && $defs{$dn}{NAME} !~ /FHEMWEB:/) { + my $hr = AttrVal($defs{$dn}{NAME}, "hiddenroom", ""); + if (index($hr,$yaahmhiddenroom) == -1){ + if ($hr eq "") { + FW_fC("attr ".$defs{$dn}{NAME}." hiddenroom ".$yaahmhiddenroom); + }else { + FW_fC("attr ".$defs{$dn}{NAME}." hiddenroom ".$hr.",".$yaahmhiddenroom); + } + Log3 $hash, 3, "[".$name. " V".$yaahmversion."]"." Added hidden room '".$yaahmhiddenroom."' to ".$defs{$dn}{NAME}; + } + } + } + + #-- Start updater + InternalTimer(gettimeofday()+ 3, "YAAHM_updater",$hash,0); + YAAHM_InternalTimer("check",gettimeofday()+ 5, "YAAHM_checkstate", $hash, 0); + +} + +######################################################################################### +# +# YAAHM_Set - Implements the Set function +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_Set($@) { + my ( $hash, $name, $cmd, @args ) = @_; + + my $imax; + my $if; + my $msg; + my $exec = ( defined($attr{$name}{"simulation"})&&$attr{$name}{"simulation"}==1 ) ? 0 : 1; + + if ( $cmd =~ /^init.*$/ ) { + return; + + #----------------------------------------------------------- + }elsif ( $cmd =~ /^manualnext.*/ ) { + #--timer address + if( $args[0] =~ /^\d+/ ) { + #-- check if valid + if( $args[0] >= int(@{$hash->{DATA}{"WT"}}) ){ + $msg = "Error, timer number ".$args[0]." does not exist, number musst be smaller than ".int( @{$hash->{DATA}{"WT"}}); + Log3 $name,1,"[YAAHM_Set] ".$msg; + return $msg; + } + $cmd = "next_".$args[0]; + }else{ + my $ifound = undef; + for( my $i=0;$i{DATA}{"WT"}});$i++){ + $ifound = $i + if ($hash->{DATA}{"WT"}[$i]{"name"} eq $args[0] ); + }; + #-- check if valid + if( !defined($ifound) ){ + $msg = "Error: timer name ".$args[0]." not found"; + Log3 $name,1,"[YAAHM_Set] ".$msg; + return $msg; + } + $cmd = "next_".$ifound; + } + return YAAHM_next($name,$cmd,$args[1],$exec); + + #----------------------------------------------------------- + }elsif ( $cmd =~ /^time.*/ ) { + return YAAHM_time($name,$args[0],$exec); + + #----------------------------------------------------------- + }elsif ( $cmd =~ /^mode.*/ ) { + return YAAHM_mode($name,$args[0],$exec); + + #----------------------------------------------------------- + }elsif ( $cmd =~ /^state.*/ ) { + return YAAHM_state($name,$args[0],$exec); + + #----------------------------------------------------------- + }elsif ( $cmd =~ /^lock(ed)?$/ ) { + readingsSingleUpdate( $hash, "lockstate", "locked", 0 ); + return; + + #----------------------------------------------------------- + } elsif ( $cmd =~ /^unlock(ed)?$/ ) { + readingsSingleUpdate( $hash, "lockstate", "unlocked", 0 ); + return; + + #----------------------------------------------------------- + } elsif ( $cmd =~ /^save/ ) { + return YAAHM_save($hash); + + #----------------------------------------------------------- + } elsif ( $cmd =~ /^restore/ ) { + return YAAHM_restore($hash); + + #----------------------------------------------------------- + } elsif ( $cmd eq "createWeekly" ){ + return "[YAAHM] missing name for new weekly profile" + if( !defined($args[0]) ); + #-- find index + $imax = int(@{$hash->{DATA}{"WT"}}); + $if= undef; + for( my $j=0;$j<$imax;$j++){ + if($hash->{DATA}{"WT"}[$j]{"name"} eq $args[0]){ + $if = $j; + last; + } + } + return "[YAAHM] name $args[0] for weekly profile to be created is already in use" + if( defined($if) ); + #-- clone wakeuptable + push(@{$hash->{DATA}{"WT"}},{%defaultwakeuptable}); + $hash->{DATA}{"WT"}[$imax]{"name"} = $args[0]; + #-- save everything + YAAHM_save($hash); + fhem("save"); + return "[YAAHM] weekly profile $args[0] created successfully"; + + #----------------------------------------------------------- + } elsif ( $cmd eq "deleteWeekly" ){ + return "[YAAHM] missing name for weekly profile to be deleted" + if( !defined($args[0]) ); + return "[YAAHM] Default weekly profile cannot be deleted" + if( ($args[0] eq $yaahm_tt->{"wakeup"}) || ($args[0] eq $yaahm_tt->{"sleep"}) ); + #-- find index + $imax = int(@{$hash->{DATA}{"WT"}}); + $if= undef; + for( my $j=0;$j<$imax;$j++){ + if($hash->{DATA}{"WT"}[$j]{"name"} eq $args[0]){ + $if = $j; + last; + } + } + return "[YAAHM] name $args[0] for weekly profile to be deleted is not known" + if( !defined($if) ); + splice(@{$hash->{DATA}{"WT"}},$if,1); + #-- delete timer + fhem("delete ".$name.".wtimer_".$if.".IF"); + #-- save everything + YAAHM_save($hash); + fhem("save"); + return "[YAAHM] weekly profile $args[0] deleted successfully"; + + #----------------------------------------------------------- + } else { + my $str = ""; + return "[YAAHM] Unknown argument " . $cmd . ", choose one of". + " manualnext time:".join(',',@times)." mode:".join(',',@modes). + " state:".join(',',@states)." locked:noArg unlocked:noArg save restore createWeekly deleteWeekly"; + } +} + +######################################################################################### +# +# YAAHM_Set - Implements the Get function +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_Get($@) { + my ($hash, @a) = @_; + my $res = ""; + my $name = $a[0]; + + my $arg = (defined($a[1]) ? $a[1] : ""); + if ($arg eq "version") { + return "YAAHM.version => $yaahmversion"; + }elsif ($arg eq "template") { + $res = "sub HouseTimeHelper(\@){\n". + " my (\$event,\$param1,\$param2) = \@_;\n\n". + " Log 1,\"[HouseTimeHelper] event=\$event\";\n\n". + " my \$time = ReadingsVal(\"".$name."\",\"housetime\",\"\");\n". + " my \$phase = ReadingsVal(\"".$name."\",\"housephase\",\"\");\n". + " my \$state = ReadingsVal(\"".$name."\",\"housestate\",\"\");\n". + " my \$party = (ReadingsVal(\"".$name."\",\"housemode\",\"\")) eq \"party\") ? 1 : 0;\n". + " my \$absence = (ReadingsVal(\"".$name."\",\"housemode\",\"\")) eq \"absence\") ? 1 : 0;\n". + " my \$todaytype = ReadingsVal(\"".$name."\",\"tr_todayType\",\"\");\n". + " my \$todaydesc = ReadingsVal(\"".$name."\",\"todayDesc\",\"\")\n". + " my \$tomorrowtype = ReadingsVal(\"".$name."\",\"tr_tomorrowType\",\"\");\n". + " my \$tomorrowdesc = ReadingsVal(\"".$name."\",\"tomorrowDesc\",\"\")\n"; + #-- iterate through table + foreach my $key (sort YAAHM_dsort keys %dailytable){ + $res .= " #---------------------------------------------------------------------\n"; + my $if = ($key eq "aftermidnight") ? "if" : "}elsif"; + $res .= " ".$if."( \$event eq \"".$key."\" ){\n\n"; + } + $res .= " }\n}\n"; + $res .= "sub HouseStateHelper(\@){\n". + " my (\$event,\$param1,\$param2) = \@_;\n\n". + " Log 1,\"[HouseStateHelper] event=\$event\";\n\n". + " my \$time = ReadingsVal(\"".$name."\",\"housetime\",\"\");\n". + " my \$phase = ReadingsVal(\"".$name."\",\"housephase\",\"\");\n". + " my \$state = ReadingsVal(\"".$name."\",\"housestate\",\"\");\n". + " my \$party = (ReadingsVal(\"".$name."\",\"housemode\",\"\")) eq \"party\") ? 1 : 0;\n". + " my \$absence = (ReadingsVal(\"".$name."\",\"housemode\",\"\")) eq \"absence\") ? 1 : 0;\n"; + #-- iterate through table + for( my $i=0;$inew->utf8; + my $jhash0 = eval{ $json->encode( $hash->{DATA} ) }; + my $error = FileWrite("YAAHMFILE",$jhash0); + return; +} + +######################################################################################### +# +# YAAHM_restore +# +# Parameter hash = hash of the YAAHM device +# +######################################################################################### + +sub YAAHM_restore($) { + my ($hash) = @_; + my ($error,$jhash0) = FileRead("YAAHMFILE"); + my $json = JSON->new->utf8; + my $jhash1 = eval{ $json->decode( $jhash0 ) }; + $hash->{DATA} = {%{$jhash1}}; + return; +} + +######################################################################################### +# +# YAAHM_setParm - Receives parameter values from the javascript FE +# +# Parameter name = name of the YAAHM device +# +######################################################################################### + +sub YAAHM_setParm($@) { + my ($name, @a) = @_; + my $hash = $defs{$name}; + + my $cmd = $a[0]; + my $key = $a[1]; + my $msg = ""; + my $val; + + #-- daily profile + # start, end/offset, execution, active in mode / daytype + if ($cmd eq "dt") { + for( my $i=1;$i<5;$i++){ + $val = $a[$i+1]; + if( ($val eq "undef")||($val eq "") ){ + $val = undef; + }elsif( ($i<3) && ($val !~ /\d?\d:\d\d/)){ + $msg = "wrong time specification $val for key $key, must be hh:mm"; + Log 1,"[YAAHM_setParm] ".$msg; + $val = "00:00"; + }elsif( $i<3 ){ + my ($hour,$min) = split(':',$val); + if( $hour>23 || $min>59 ){ + $msg = "wrong time specification $val for key $key > 23:59"; + Log 1,"[YAAHM_setParm] ".$msg; + $val = "00:00"; + } + } + $hash->{DATA}{"DT"}{$key}[$i-1]=$val; + #Log 1, "=============> $key $i ".$a[$i+1]; + } + return $msg; + #-- weekly profile + }elsif ($cmd eq "wt") { + #Log 1,"=================> ".Dumper(@a); + #-- action + $hash->{DATA}{"WT"}[$a[1]]{"action"} = $a[2]; + #-- next time + $val = $a[3]; + if( ($val eq "undef")||($val eq "") ){ + $val = undef; + }elsif( $val !~ /\d?\d:\d\d/ ){ + $msg = "wrong time specification next=$val for weekly timer ".$a[1].", must be hh:mm"; + Log 1,"[YAAHM_setParm] ".$msg; + $val = "07:00"; + }else{ + my ($hour,$min) = split(':',$val); + if( $hour>23 || $min>59 ){ + $msg = "wrong time specification next=$val for weekly timer > 23:59".$a[1]; + Log 1,"[YAAHM_setParm] ".$msg; + $val = "07:00"; + } + } + $hash->{DATA}{"WT"}[$a[1]]{"next"} = $val; + $hash->{DATA}{"WT"}[$a[1]]{"acti_m"} = $a[4]; + $hash->{DATA}{"WT"}[$a[1]]{"acti_d"} = $a[5]; + #-- weekdays + for( my $i=0;$i<7;$i++){ + $val = $a[$i+6]; + if( ($val eq "undef")||($val eq "") ){ + $val = undef; + }elsif( ($i<3) && ($val !~ /\d?\d:\d\d/)){ + $msg = "wrong time specification $val for weekly timer ".$a[1].", must be hh:mm"; + Log 1,"[YAAHM_setParm] ".$msg; + $val = "07:00"; + }elsif( $val =~/^off/ ){ + #-- ok + }else{ + #-- ok + my ($hour,$min) = split(':',$val); + if( $hour>23 || $min>59 ){ + $msg = "wrong time specification $val for weekly timer > 23:59 ".$a[1]; + Log 1,"[YAAHM_setParm] ".$msg; + $val = "07:00"; + } + } + $hash->{DATA}{"WT"}[$a[1]]{$weeklytable[$i]} = $val; + } + return $msg; + } +} + +######################################################################################### +# +# YAAHM_time - Change the house time (aftermidnight .. beforemidnight) +# +# Parameter name = name of the YAAHM device +# +######################################################################################### + +sub YAAHM_time { + my ($name,$targettime,$exec) = @_; + my $hash = $defs{$name}; + + my $prevtime = defined($hash->{DATA}{"HSM"}{"time"}) ? $hash->{DATA}{"HSM"}{"time"} : ""; + my $currmode = defined($hash->{DATA}{"HSM"}{"mode"}) ? $hash->{DATA}{"HSM"}{"mode"} : "normal"; + my $currstate = defined($hash->{DATA}{"HSM"}{"state"}) ? $hash->{DATA}{"HSM"}{"state"} : "unsecured"; + my $msg = ""; + + #-- local checks + #-- double change + #if( $prevtime eq $targettime ){ + # $msg = "Transition skipped, the time event $targettime has been executed already"; + #} + + #-- don't + if( $msg ne "" ){ + Log3 $name,1,"[YAAHM_time] ".$msg; + return $msg; + } + #-- doit + my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time); + my $lval = sprintf("%02d%02d",$hour,$min); + my $mval = $dailytable{"morning"}[0]; + my $nval = $dailytable{"night"}[0]; + my $tval = $dailytable{$targettime}[0]; + + $mval =~ s/://; + $nval =~ s/://; + $tval =~ s/://; + + #-- targetphase always according to real time, not to command time + my $targetphase = ( ($lval >= $mval) && ( $nval > $lval ) ) ? "daytime" : "nighttime"; + + #-- iterate through table to find next event + my $nexttime; + my $sval; + my $oval="0000"; + foreach my $key (sort YAAHM_dsort keys %dailytable){ + $nexttime = $key; + $sval = $dailytable{$key}[0]; + next + if (!defined($sval)); + $sval =~ s/://; + last + if ( ($lval <= $sval) && ( $lval > $oval ) ); + $oval = $sval; + } + + #-- automatically leave party mode at morning time or when going to bed + if( $currmode eq "party" && $targettime =~ /(morning)|(sleep)/ && defined($attr{$name}{"modeAuto"}) && $attr{$name}{"modeAuto"} == 1 ){ + $msg = YAAHM_mode($name,"normal",$exec)."\n"; + $msg .= YAAHM_state($name,"secured",$exec)."\n" + if( $currstate eq "unsecured" && $targettime eq "sleep" && defined($attr{$name}{"stateAuto"}) && $attr{$name}{"stateAuto"} == 1 ); + + #-- automatically leave absence mode at wakeup time + }elsif( $currmode eq "absence" && $targettime =~ /(wakeup)/ && defined($attr{$name}{"modeAuto"}) && $attr{$name}{"modeAuto"} == 1 ){ + $msg = YAAHM_mode($name,"normal",$exec)."\n"; + + #-- automatically secure the house at night time or when going to bed (if not absence, and if not party) + }elsif( $currmode eq "normal" && $currstate eq "unsecured" && $targettime =~ /(night)|(sleep)/ && defined($attr{$name}{"stateAuto"}) && $attr{$name}{"stateAuto"} == 1 ){ + $msg = YAAHM_state($name,"secured",$exec)."\n"; + } + + $hash->{DATA}{"HSM"}{"time"} = $targettime; + + YAAHM_checkMonthly($hash,'event',$targettime); + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"prev_housetime",$prevtime); + readingsBulkUpdate($hash,"next_housetime",$nexttime); + readingsBulkUpdate($hash,"housetime",$targettime); + readingsBulkUpdate($hash,"tr_housetime",$yaahm_tt->{$targettime}); + readingsBulkUpdate($hash,"housephase",$targetphase); + readingsBulkUpdate($hash,"tr_housephase",$yaahm_tt->{$targetphase}); + readingsEndUpdate($hash,1); + + #-- execute function not required by call from external timer + return + if( !defined($exec) ); + + #-- doit + my $xval; + my $ival; + my $wupn; + if( $targettime eq "wakeup" ){ + $wupn = $hash->{DATA}{"WT"}[0]{"name"}; + $ival = (ReadingsVal($name.".wtimer_0.IF","mode","") ne "disabled"); + $xval = $ival ? $hash->{DATA}{"WT"}[0]{"action"} : ""; + $msg .= "Simulation ".$xval." from weekly profile ".$wupn; + $msg .= " (disabled)" + if !$ival; + }elsif( $targettime eq "sleep" ){ + $wupn = $hash->{DATA}{"WT"}[1]{"name"}; + $ival = (ReadingsVal($name.".wtimer_1.IF","mode","") ne "disabled"); + $xval = $ival ? $hash->{DATA}{"WT"}[1]{"action"} : ""; + $msg .= "Simulation ".$xval." from weekly profile ".$wupn; + $msg .= " (disabled)" + if !$ival; + }else{ + $xval = $dailytable{$targettime}[2]; + $msg .= "Simulation ".$xval; + } + if( $exec==1 ){ + fhem($xval); + }elsif( $exec==0 ){ + Log3 $name,1,"[YAAHM_time] ".$msg; + return $msg; + } +} + +######################################################################################### +# +# YAAHM_next - set the next weekly time +# +# Parameter name = name of device addressed +# +######################################################################################### + +sub YAAHM_next { + my ($name,$cmd,$time,$exec) = @_; + + my $hash = $defs{$name}; + my $msg; + + #--determine which timer (duplicate check when coming from set) + $cmd =~ /manualnext_([0-9]+)/; + my $i = $1; + + if( $i >= int( @{$hash->{DATA}{"WT"}}) ){ + $msg = "Error, timer number $i does not exist, number musst be smaller than ".int( @{$hash->{DATA}{"WT"}}); + Log3 $name,1,"[YAAHM_next] ".$msg; + return $msg; + } + + #-- check value - may be empty + if( $time ne ""){ + #-- off=ok, do nothing + if( $time eq "off"){ + #-- time=ok, check + }elsif( $time =~ /(\d?\d):(\d\d)(:(\d\d))?/ ){ + if( $1 >= 24 || $2 >= 60){ + $msg = "Error, time specification $time for timer ".$cmd." > 23:59 "; + Log3 $name,1,"[YAAHM_next] ".$msg; + return $msg; + } + $time = sprintf("%02d:\%02d",$1,$2); + }else{ + $msg = "Error, time specification $time invalid for timer ".$cmd.", must be hh:mm";; + Log3 $name,1,"[YAAHM_next] ".$msg; + return $msg; + } + } + + #-- weekly profile times + my $sg0; + if( ReadingsVal($name.".wtimer_".$i.".IF","mode","") ne "disabled" ){ + $sg0 = $time; + }else{ + $sg0 = "off"; + } + $hash->{DATA}{"WT"}[$i]{"next"} = $sg0; + YAAHM_setWeeklyTime($hash); + + readingsEndUpdate($hash,1); +} + +######################################################################################### +# +# YAAHM_mode - Change the house mode (normal, party, absence) +# +# Parameter name = name of the YAAHM device +# +######################################################################################### + +sub YAAHM_mode { + my ($name,$targetmode,$exec) = @_; + my $hash = $defs{$name}; + + my $prevmode = defined($hash->{DATA}{"HSM"}{"mode"}) ? $hash->{DATA}{"HSM"}{"mode"} : "normal"; + my $currstate = defined($hash->{DATA}{"HSM"}{"state"}) ? $hash->{DATA}{"HSM"}{"state"} : "unsecured"; + my $msg = ""; + + #-- local checks + #-- double change + if( $prevmode eq $targetmode ){ + $msg = "transition skipped, we are already in $targetmode mode"; + + #-- transition into party and absence is only possible from normal mode + }elsif( $prevmode ne "normal" && $targetmode ne "normal"){ + $msg = "transition into $targetmode mode is only possible from normal mode"; + + + #-- global checks + #-- transition into party mode only possible in unlocked state + }elsif( $targetmode eq "party" && $currstate ne "unsecured" ){ + $msg = "transition into party mode is only possible in unsecured state" + } + + #-- don't + if( $msg ne "" ){ + Log3 $name,1,"[YAAHM_mode] ".$msg; + return $msg; + } + + $hash->{DATA}{"HSM"}{"mode"} = $targetmode; + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"prev_housemode",$prevmode); + readingsBulkUpdate($hash,"housemode",$targetmode); + readingsBulkUpdate($hash,"tr_housemode",$yaahm_tt->{$targetmode}); + readingsEndUpdate($hash,1); + + #-- doit, if not simulation + if (defined($attr{$name}{"modeHelper"})){ + if( !defined($exec) || $exec==1 ){ + fhem("{".$attr{$name}{"modeHelper"}."('".$targetmode."')}"); + }else{ + $msg = "Simulation {".$attr{$name}{"modeHelper"}."('".$targetmode."')}"; + Log3 $name,1,"[YAAHM_mode] ".$msg; + return $msg; + } + } +} + +######################################################################################### +# +# YAAHM_state - Change the house state (unscured, secured, guarded) +# +# Parameter name = name of the YAAHM device +# +######################################################################################### + +sub YAAHM_state { + my ($name,$targetstate,$exec) = @_; + my $hash = $defs{$name}; + + my $prevstate = defined($hash->{DATA}{"HSM"}{"state"}) ? $hash->{DATA}{"HSM"}{"state"} : "unsecured"; + my $currmode = defined($hash->{DATA}{"HSM"}{"mode"}) ? $hash->{DATA}{"HSM"}{"mode"} : "normal"; + my $msg = ""; + + #-- local checks + #-- double change + if( $prevstate eq $targetstate ){ + $msg = "transition skipped, we are already in $targetstate state"; + + #-- global checks + #-- changing away from unlocked in party mode is not possible + }elsif( $targetstate ne "unlocked" && $currmode eq "party" ){ + $msg = "not possible in party mode"; + } + + #-- don't + if( $msg ne "" ){ + Log3 $name,1,"[YAAHM_state] ".$msg; + return $msg; + } + + $hash->{DATA}{"HSM"}{"state"} = $targetstate; + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"prev_housestate",$prevstate); + readingsBulkUpdate($hash,"housestate",$targetstate); + readingsBulkUpdate($hash,"tr_housestate",$yaahm_tt->{$targetstate}); + readingsEndUpdate($hash,1); + + YAAHM_checkstate($hash); + + #-- doit, if not simulation + if (defined($attr{$name}{"stateHelper"})){ + if( !defined($exec) || $exec==1 ){ + fhem("{".$attr{$name}{"stateHelper"}."('".$targetstate."')}"); + }else{ + $msg = "Simulation {".$attr{$name}{"stateHelper"}."('".$targetstate."')}"; + Log3 $name,1,"[YAAHM_state] ".$msg; + return $msg; + } + } +} + + +######################################################################################### +# +# YAAHM_checkstate - check state devices +# +# Parameter someHash = either internal hash of timer +# => need to dereference it for getting device hash +# or device hash +# +######################################################################################### + +sub YAAHM_checkstate($) { + my ($someHash) = @_; + + my $hash; + if( defined($someHash->{HASH}) ){ + $hash = $someHash->{HASH}; + }else{ + $hash = $someHash; + } + my $name = $hash->{NAME}; + + my $next; + + $next = gettimeofday()+AttrVal($name,"stateInterval",60)*60; + + YAAHM_RemoveInternalTimer("check",$hash); + YAAHM_InternalTimer("check",$next, "YAAHM_checkstate", $hash, 0); + Log3 $name, 5,"[YAAHM_checkstate] on device ".$hash->{NAME}." called"; + + my $istate; + my $cstate = defined($hash->{DATA}{"HSM"}{"state"}) ? $hash->{DATA}{"HSM"}{"state"} : ""; + + return undef + if( !defined($attr{$name}{"stateDevices"}) ); + + for($istate=0;$istate Device $dev SOLL $devs IST $devh"; + $isf = 1; + push(@devf,"".$dev."
".$yaahm_tt->{'notok'}. + "
".$devh.""); + if( defined(AttrVal($name,"stateWarning",undef)) ){ + fhem("{".AttrVal($name,"stateWarning",undef)."($dev,$devs,$devh)}"); + } + }else{ + push(@devf,"".$dev."
".$yaahm_tt->{'ok'}."
"); + } + } + } + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"sdev_housestate","".join("
",@devf)."
"); + readingsBulkUpdate($hash,"sec_housestate",(($isf==0)?"secure":"insecure")); + readingsBulkUpdate($hash,"sym_housestate",(($isf==0)?"
":"
")); + readingsEndUpdate($hash,1); + + return undef +} + +######################################################################################### +# +# YAAHM_SM - State machine +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_SM($) { + my ($hash) = @_; + +} + +######################################################################################### +# +# YAAHM_informer - Tell FHEMWEB to inform this page +# +# Parameter me = hash of FHEMWEB instance +# +######################################################################################### + +sub YAAHM_informer($) { + my ($me) = @_; + + $me->{inform}{type} = "status"; + $me->{inform}{filter} = "YYY"; + #$me->{inform}{since} = time()-5; + $me->{inform}{fmt} = "JSON"; + + my $filter = $me->{inform}{filter}; + + my %h = map { $_ => 1 } devspec2array($filter); + + $h{global} = 1 if( $me->{inform}{addglobal} ); + $h{"#FHEMWEB:$FW_wname"} = 1; + $me->{inform}{devices} = \%h; + %FW_visibleDeviceHash = FW_visibleDevices(); + + $me->{NTFY_ORDER} = $FW_cname; # else notifyfn won't be called + %ntfyHash = (); +} + +######################################################################################### +# +# YAAHM_startDayTimer - start the daily timer function +# +# Parameter name = name of the YAAHM device +# +######################################################################################### + +sub YAAHM_startDayTimer($) { + my ($name) = @_; + my $hash = $defs{$name}; + + my $res = "defmod $name.dtimer.IF DOIF "; + my $msg; + + YAAHM_Sun($hash); + + #--cleanup after definition fault + fhem("deletereading $name t_aftermidnight") + if( ReadingsVal($name,"t_aftermidnight",undef) ); + fhem("deletereading $name t_aftersunrise") + if( ReadingsVal($name,"t_aftersunrise",undef) ); + fhem("deletereading $name t_aftersunset") + if( ReadingsVal($name,"t_aftersunset",undef) ); + fhem("deletereading $name t_beforemidnight") + if( ReadingsVal($name,"t_beforemidnight",undef) ); + fhem("deletereading $name t_beforesunrise") + if( ReadingsVal($name,"t_beforesunrise",undef) ); + fhem("deletereading $name t_beforesunset") + if( ReadingsVal($name,"t_beforesunset",undef) ); + + delete $hash->{DATA}{"DT"}{"daytime"}; + delete $hash->{DATA}{"DT"}{"nighttime"}; + + #-- TODO check for plausibility + #--aftermidnight must be >= 00:01 + my $check=$hash->{DATA}{"DT"}{"aftermidnight"}[0]; + $check =~ s/://; + if( $check <= 0 ){ + Log3 $name,1,"[YAAHM_startDayTimer] aftermidnight is minimum 00:01, used this value"; + $hash->{DATA}{"DT"}{"aftermidnight"}[0] = "00:01"; + } + + #-- Internal timer for night time + my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time); + my $nval = $hash->{DATA}{"DT"}{"night"}[0]; + if( $nval !~ /\d\d:\d\d/ ){ + $msg = "Error in night time specification"; + Log3 1,$name,"[YAAHM_startDayTimer] ".$msg; + return $msg; + } + my ($hourn,$minn) = split(':',$nval); + my $deltan = ($hourn-$hour)*3600+($minn-$min)*60-$sec; + my $delta = $deltan; + $delta += 86400 + if( $delta<0 ); + YAAHM_RemoveInternalTimer ("nighttime", $hash); + YAAHM_InternalTimer ("nighttime", gettimeofday()+$delta, "YAAHM_tonight", $hash, 0); + + #-- Internal timer for daytime + my $mval = $hash->{DATA}{"DT"}{"morning"}[0]; + if( $mval !~ /\d\d:\d\d/ ){ + $msg = "Error in morning time specification"; + Log3 1,$name,"[YAAHM_startDayTimer] ".$msg; + return $msg; + } + ($hourn,$minn) = split(':',$mval); + my $deltam = ($hourn-$hour)*3600+($minn-$min)*60-$sec; + $delta = $deltam; + $delta += 86400 + if( $delta<0 ); + YAAHM_RemoveInternalTimer ("daytime", $hash); + YAAHM_InternalTimer ("daytime", gettimeofday()+$delta, "YAAHM_today", $hash, 0); + + #-- currently day or night ? + my $currtime = (($deltam < 0) && ($deltan > 0)) ? "daytime" : "nighttime"; + + #-- put data into readings + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"housephase",$currtime); + readingsBulkUpdate($hash,"tr_housephase",$yaahm_tt->{$currtime}); + + #-- compose external timer + foreach my $key (sort YAAHM_dsort keys %defaultdailytable){ + next if( !defined($hash->{DATA}{"DT"}{$key}[2]) ); + my $f1 = defined($defaultdailytable{$key}[0]); + my $f2 = defined($defaultdailytable{$key}[1]); + my $f3 = defined($hash->{DATA}{"DT"}{$key}[2]) && $hash->{DATA}{"DT"}{$key}[2] ne ""; + + + my $xval = "{YAAHM_time('".$name."','".$key."')},".$hash->{DATA}{"DT"}{$key}[2]; + + #-- entries in the default table with no entry are single-timers + if( !$f1 and !$f2 ){ + readingsBulkUpdate( $hash, "s_".$key, $hash->{DATA}{"DT"}{$key}[0] ); + $res .= "([[".$name.":s_".$key."]])\n(".$xval.")\nDOELSEIF" + if( $f3 ); + + #-- entries in the default table with only first time are single-timers + }elsif( $f1 and !$f2 ){ + readingsBulkUpdate( $hash, "s_".$key, $hash->{DATA}{"DT"}{$key}[0] ); + $res .= "([[".$name.":s_".$key."]])\n(".$xval.")\nDOELSEIF" + if( $f3 ); + + #-- entries in the default table with only second time are single-timer offsets + }elsif( !$f1 and $f2 ){ + readingsBulkUpdate( $hash, "s_".$key, $hash->{DATA}{"DT"}{$key}[0] ); + $res .= "([[".$name.":s_".$key."]])\n(".$xval.")\nDOELSEIF" + if( $f3 ); + + #-- entries in the default table with first and second time are two-timer periods + }elsif( $f1 and $f2 ){ + readingsBulkUpdate( $hash, "s_".$key, $hash->{DATA}{"DT"}{$key}[0] ); + readingsBulkUpdate( $hash, "t_".$key, $hash->{DATA}{"DT"}{$key}[1] ); + $res .= "([[".$name.":s_".$key."]-[".$name.":t_".$key."]])\n(".$xval.")\nDOELSEIF" + if( $f3 ); + + #-- something wrong + }else{ + $msg = "Daily timer $name.dtimer.IF NOT started, something wrong with entry ".$key; + Log 1,"[YAAHM_startDayTimer] ".$msg; + return $msg; + } + } + readingsEndUpdate($hash,1); + + #-- take out last DOELSEIF + $res =~ s/\nDOELSEIF$//; + fhem($res); + fhem("attr $name.dtimer.IF do always"); + fhem("set $name.dtimer.IF enable"); + + #-- save everything + YAAHM_save($hash); + fhem("save"); + + return "Daily timer $name.dtimer.IF started"; +} + +######################################################################################### +# +# YAAHM_startWeeklyTimer - start the Weekly timer function +# +# Parameter name = name of the YAAHM device +# +######################################################################################### + +sub YAAHM_startWeeklyTimer($) { + my ($name) = @_; + my $hash = $defs{$name}; + + my $res; + my $wupn; + + YAAHM_setWeeklyTime($hash); + + #-- start timer + for( my $i=0;$i < int( @{$hash->{DATA}{"WT"}} );$i++){ + $wupn = $hash->{DATA}{"WT"}[$i]{"name"}; + $res = "defmod ".$name.".wtimer_".$i.".IF DOIF ([".$name.":ring_".$i."] eq \"off\")\n()\nDOELSEIF\n(([[".$name.":ring_".$i."]])"; + + #-- check for activity description + my $g4a = defined($hash->{DATA}{"WT"}[$i]{"acti_m"}) ? $hash->{DATA}{"WT"}[$i]{"acti_m"} : ""; + my $g4b = defined($hash->{DATA}{"WT"}[$i]{"acti_d"}) ? $hash->{DATA}{"WT"}[$i]{"acti_d"} : ""; + my $v4a = ($g4a ne "") ? "(normal)|(".join(')|(',split(',',$g4a)).")" : "(normal)"; + my $v4b = ($g4b ne "") ? "(workday)|(weekend)|(".join(')|(',split(',',$g4b)).")" : "(workday)|(weekend)"; + + $res .= "\nand ([" .$name. ":housemode] =~ \"".$v4a."\")"; + $res .= "\nand ([" .$name. ":todayType] =~ \"".$v4b."\")"; + + #-- action + my $xval = ""; + if( $i==0 ){ + $xval = "{YAAHM_time('".$name."','wakeup')},".$hash->{DATA}{"WT"}[$i]{"action"}; + }elsif( $i==1 ){ + $xval = "{YAAHM_time('".$name."','sleep')},".$hash->{DATA}{"WT"}[$i]{"action"}; + }else{ + $xval = $hash->{DATA}{"WT"}[$i]{"action"}; + } + + #-- action + $res .= ")\n(".$xval.")"; + + #-- doit + fhem($res); + fhem("attr ".$name.".wtimer_".$i.".IF do always"); + fhem("set ".$name.".wtimer_".$i.".IF enable"); + } + + #-- save everything + YAAHM_save($hash); + fhem("save"); + + return "Weekly timers started"; +} + +######################################################################################### +# +# YAAHM_setWeeklyTime - set the Weekly times into readings +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_setWeeklyTime($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + + readingsBeginUpdate($hash); + + #-- weekly profile times + my ($sg0,$sg1,$sg2,$wt,$ng,$mt); + + #-- iterate over timers + for( my $i=0;$i{DATA}{"WT"}} );$i++){ + if( ReadingsVal($name.".wtimer_".$i.".IF","mode","") ne "disabled" ){ + $sg0 = $hash->{DATA}{"WT"}[$i]{ $weeklytable[$hash->{DATA}{"DD"}[0]{"weekday"}] } ; + $sg1 = $hash->{DATA}{"WT"}[$i]{ $weeklytable[$hash->{DATA}{"DD"}[1]{"weekday"}] }; + $ng = $hash->{DATA}{"WT"}[$i]{ "next" }; + }else{ + $sg0 = "off"; + $sg1 = "off"; + $ng = "off" + } + #-- + $mt = $sg1; + + #-- now check if next time is already past + my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time); + my $lga = sprintf("%02d%02d",$hour,$min); + my $nga = $ng; + $nga =~ s/://; + + #-- arbitrary today, next off + # set waketime off, erase next + if( $nga eq "off"){ + $wt = "off"; + $ng = ""; + + #-- arbitrary today, next undefined + # set waketime to today + }elsif( $nga eq ""){ + $wt = $sg0; + + #-- arbitrary today, but next nontrivial and before current time + # set waketime to today, leave next as it is (=> tomorrow) + }elsif( $nga ne "off" && $nga < $lga ){ + $wt = $sg0; + $mt = $sg1." (".$ng.")"; + + #-- arbitrary today, but next nontrivial and after current time + # replace waketime by next, erase next + }elsif( $nga ne "off" && $nga > $lga){ + $wt = $ng; + $ng = ""; + } + + readingsBulkUpdate( $hash, "ring_".$i,$wt ); + readingsBulkUpdate( $hash, "next_".$i,$ng ); + readingsBulkUpdate( $hash, "today_".$i,$sg0 ); + readingsBulkUpdate( $hash, "tomorrow_".$i,$sg1 ); + readingsBulkUpdate( $hash, "ring_".$i."_1",$mt ); + } + + readingsEndUpdate($hash,1); +} + +######################################################################################### +# +# YAAHM_checkMonthly - check Monthly calendar at each +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_checkMonthly($$$) { + my ($hash,$event,$param) = @_; + + my $name = $hash->{NAME}; + + my ($ret,$line,$fline,$date); + my (@lines,@chunks,@tday,@eday,@sday,@tmor,@two); + my ($stoday,$stom,$stwom); + my $todaylong = ""; + my $tomlong = ""; + my $twodaylong= ""; + + #-- hourly call + #if( ($event eq 'hour') || ($event eq '') ){ + # my $text; + # $text = fhem("get Muell text modeAlarmOrStart"); + # $text = "--" if (!$text); + # $text = "--" if ($text eq ""); + # #fhem("set Termin ".$text); + # return $text; + + #-- Vorschau täglich oder wenn neu gestartet + if( ($event eq "test") || (($event eq 'housetime') && ($param eq 'aftermidnight')) ){ + + my $specialDevs = AttrVal( $name, "specialDevices", "" ); + foreach my $specialDev ( split( /,/, $specialDevs ) ) { + my ($todaydesc,$tomdesc,$twomdesc); + #-- device of type holiday + if( IsDevice( $specialDev, "holiday" )){ + $stoday = strftime('%2m-%2d', localtime(time)); + $stom = strftime('%2m-%2d', localtime(time+86400)); + $stwom = strftime('%2m-%2d', localtime(time+2*86400)); + my $tod = holiday_refresh( $specialDev, $stoday ); + if ( $tod ne "none" ) { + $todaydesc = $tod; + Log3 $name, 5,"[YAAHM] found today=special date \"$todaydesc\" in holiday $specialDev"; + } + $tod = holiday_refresh( $specialDev, $stom ); + if ( $tod ne "none" ) { + $tomdesc = $tod; + Log3 $name, 5,"[YAAHM] found tomorrow=special date \"$tomdesc\" in holiday $specialDev"; + } + $tod = holiday_refresh( $specialDev, $stwom ); + if ( $tod ne "none" ) { + $twomdesc = $tod; + Log3 $name, 5,"[YAAHM] found twodays=special date \"$twomdesc\" in holiday $specialDev"; + } + #-- device of type calendar + }elsif( IsDevice($specialDev, "Calendar" )){ + $stoday = strftime('%2d.%2m.%2Y', localtime(time)); + $stom = strftime('%2d.%2m.%2Y', localtime(time+86400)); + $stwom = strftime('%2d.%2m.%2Y', localtime(time+2*86400)); + @tday = split('\.',$stoday); + @tmor = split('\.',$stom); + @two = split('\.',$stwom); + #-- more complicated to check here + $fline=Calendar_Get($defs{$specialDev},"get","full","mode=alarm|start|upcoming"); + + if($fline){ + #chomp($fline); + @lines = split('\n',$fline); + foreach $fline (@lines){ + chomp($fline); + @chunks = split(' ',$fline); + @sday = split('\.',$chunks[4]); + #-- today + my $rets = ($sday[2]-$tday[2])*365+($sday[1]-$tday[1])*31+($sday[0]-$tday[0]); + if( $rets==0 ){ + $todaydesc = $chunks[7]; + Log3 $name, 5,"[YAAHM] found today=special date \"$todaydesc\" in calendar $specialDev"; + } + $rets = ($sday[2]-$tmor[2])*365+($sday[1]-$tmor[1])*31+($sday[0]-$tmor[0]); + if( $rets==0 ){ + $tomdesc = $chunks[7]; + Log3 $name, 5,"[YAAHM] found tomorrow=special date \"$tomdesc\" in calendar $specialDev"; + } + $rets = ($sday[2]-$two[2])*365+($sday[1]-$two[1])*31+($sday[0]-$two[0]); + if( $rets==0 ){ + $twomdesc = $chunks[7]; + Log3 $name, 5,"[YAAHM] found twodays=special date \"$twomdesc\" in calendar $specialDev"; + } + } + } + }else{ + Log3 $name, 1,"[YAAHM] unknown special device $specialDev"; + + } + #-- accumulate descriptions + $todaylong .= $todaydesc.',' + if($todaydesc); + $todaylong =~ s/,$//; + $tomlong .= $tomdesc.',' + if($tomdesc); + $tomlong =~ s/,$//; + $twodaylong .= $twomdesc.',' + if($twomdesc); + $twodaylong =~ s/,$//; + + } + $hash->{DATA}{"DD"}[0]{"special"} = $todaylong; + $hash->{DATA}{"DD"}[1]{"special"} = $tomlong; + #-- put into readings + readingsBeginUpdate($hash); + readingsBulkUpdateIfChanged( $hash, "todaySpecial",$todaylong ); + readingsBulkUpdateIfChanged( $hash, "tomorrowSpecial",$tomlong); + readingsEndUpdate($hash,1); + } +} + +######################################################################################### +# +# YAAHM_today - internal function to switch into daytime +# +# Parameter timerHash = internal hash of timer +# => need to dereference it for getting device hash +# +######################################################################################### + +sub YAAHM_today($) { + my ($timerHash) = @_; + my $next; + + my $hash = $timerHash->{HASH}; + my $name = $hash->{NAME}; + + $next = gettimeofday()+86400; + + YAAHM_RemoveInternalTimer("today",$hash); + YAAHM_InternalTimer("today",$next, "YAAHM_today", $hash, 0); + Log 1,"[YAAHM_today] on device ".$hash->{NAME}." called for this day"; + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"housephase","daytime"); + readingsBulkUpdate($hash,"tr_housephase",$yaahm_tt->{"daytime"}); + readingsEndUpdate($hash,1); + + return undef; +} + +######################################################################################### +#d +# YAAHM_tonight - internal function to switch into nighttime +# +# Parameter timerHash = internal hash of timer +# => need to dereference it for getting device hash +# +######################################################################################### + +sub YAAHM_tonight($) { + my ($timerHash) = @_; + my $next; + + my $hash = $timerHash->{HASH}; + my $name = $hash->{NAME}; + + $next = gettimeofday()+86400; + + YAAHM_RemoveInternalTimer("tonight",$hash); + YAAHM_InternalTimer("tonight",$next, "YAAHM_tonight", $hash, 0); + Log 1,"[YAAHM_tonight] on device ".$hash->{NAME}." called for this day"; + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"housephase","nighttime"); + readingsBulkUpdate($hash,"tr_housephase",$yaahm_tt->{"nighttime"}); + readingsEndUpdate($hash,1); + + return undef; +} + +######################################################################################### +# +# YAAHM_updater - internal update function 1 minute after midnight +# +# Parameter timerHash = on first call, device hash. +# = on later calls: internal hash of timer +# => need to dereference it for getting device hash +# +######################################################################################### + +sub YAAHM_updater($) { + my ($timerHash) = @_; + my $hash; + my $next; + + #-- start timer for updates - when device is reloaded + if( defined($firstcall) && ($firstcall==1) ){ + #-- timerHash is device hash + $hash = $timerHash; + my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time); + $next = gettimeofday()+(23-$hour)*3600+(59-$min)*60+(59-$sec)+34; + $firstcall=0; + #-- continue timer for updates + }else{ + #-- timerHash is internal hash + $hash = $timerHash->{HASH}; + $next = gettimeofday()+86400; + } + #-- safeguard if hash is not properly indirected + if( defined($hash->{HASH}) ){ + #Log 1,"WARNING ! HASH indirection not ok. firstcall=$firstcall"; + $hash = $hash->{HASH}; + } + YAAHM_RemoveInternalTimer("aftermidnight",$hash); + YAAHM_InternalTimer("aftermidnight",$next, "YAAHM_updater", $hash, 0); + Log 1,"[YAAHM_updater] on device ".$hash->{NAME}." called for this day"; + + YAAHM_GetDayStatus($hash); + + return undef; +} + +######################################################################################### +# +# YAAHM_InternalTimer - start named internal timer +# +# Parameter modifier = name suffix +# tim = time +# callback = callback function +# +# +######################################################################################### + +sub YAAHM_InternalTimer($$$$$) { + my ($modifier, $tim, $callback, $hash, $waitIfInitNotDone) = @_; + + my $mHash; + if ($modifier eq "") { + $mHash = $hash; + } else { + my $timerName = "$hash->{NAME}_$modifier"; + if (exists ($hash->{TIMER}{$timerName})) { + $mHash = $hash->{TIMER}{$timerName}; + } else { + $mHash = { HASH=>$hash, NAME=>"$hash->{NAME}_$modifier", MODIFIER=>$modifier}; + $hash->{TIMER}{$timerName} = $mHash; + } + } + InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone); +} + +######################################################################################### +# +# YAAHM_RemoveInternalTimer - kill named internal timer +# +# Parameter +# +######################################################################################### + +sub YAAHM_RemoveInternalTimer($$) { + my ($modifier, $hash) = @_; + + my $timerName = "$hash->{NAME}_$modifier"; + if ($modifier eq "") { + RemoveInternalTimer($hash); + } else { + my $myHash = $hash->{TIMER}{$timerName}; + if (defined($myHash)) { + delete $hash->{TIMER}{$timerName}; + RemoveInternalTimer($myHash); + } + } +} + +######################################################################################### +# +# YAAHM_GetDayStatus +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_GetDayStatus($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + + #-- readjust language + my $lang = AttrVal("global","language","EN"); + if( $lang eq "DE"){ + $yaahm_tt = \%yaahm_transtable_DE; + }else{ + $yaahm_tt = \%yaahm_transtable_EN; + } + + my ($ret,$line,$fline,$date); + my (@lines,@chunks,@tday,@eday,@sday,@tmor); + my ($todaydesc,$todaytype,$tomdesc,$tomtype); + + my $stoday = strftime('%2d.%2m.%2Y', localtime(time)); + my $stom = strftime('%2d.%2m.%2Y', localtime(time+86400)); + + #-- workday has lowest priority + $todaytype = "workday"; + $hash->{DATA}{"DD"}[0]{"date"} = $stoday; + $hash->{DATA}{"DD"}[0]{"weekday"} = (strftime('%w', localtime(time))+6)%7; + $hash->{DATA}{"DD"}[0]{"daytype"} = "workday"; + $hash->{DATA}{"DD"}[0]{"desc"} = $yaahm_tt->{"workday"}; + + $tomtype = "workday"; + $hash->{DATA}{"DD"}[1]{"date"} = $stom; + $hash->{DATA}{"DD"}[1]{"weekday"} = (strftime('%w', localtime(time+86400))+6)%7; + $hash->{DATA}{"DD"}[1]{"daytype"} = "workday"; + $hash->{DATA}{"DD"}[1]{"desc"} = $yaahm_tt->{"workday"}; + + #-- vacation = vacdays has higher priority + my $vacdayDevs = AttrVal( $name, "vacationDevices", "" ); + foreach my $vacdayDev ( split( /,/, $vacdayDevs ) ) { + #-- device of type holiday + if( IsDevice( $vacdayDev, "holiday" )){ + $stoday = strftime('%2m-%2d', localtime(time)); + $stom = strftime('%2m-%2d', localtime(time+86400)); + my $tod = holiday_refresh( $vacdayDev, $stoday ); + if ( $tod ne "none" ) { + $todaydesc = $tod; + $todaytype = "vacday"; + Log3 $name, 1,"[YAAHM] found today=vacation \"$todaydesc\" in holiday $vacdayDev"; + } + $tod = holiday_refresh( $vacdayDev, $stom ); + if ( $tod ne "none" ) { + $tomdesc = $tod; + $tomtype = "vacday"; + Log3 $name, 1,"[YAAHM] found tomorrow=vacation \"$tomdesc\" in holiday $vacdayDev"; + } + #-- device of type calendar + }elsif( IsDevice($vacdayDev, "Calendar" )){ + $stoday = strftime('%2d.%2m.%2Y', localtime(time)); + $stom = strftime('%2d.%2m.%2Y', localtime(time+86400)); + @tday = split('\.',$stoday); + @tmor = split('\.',$stom); + #-- more complicated to check here + $fline=Calendar_Get($defs{$vacdayDev},"get","full","mode=alarm|start|upcoming"); + if($fline){ + #chomp($fline); + @lines = split('\n',$fline); + foreach $fline (@lines){ + chomp($fline); + @chunks = split(' ',$fline); + @sday = split('\.',$chunks[2]); + @eday = split('\.',substr($chunks[3],9,10)); + #-- today + my $rets = ($sday[2]-$tday[2])*365+($sday[1]-$tday[1])*31+($sday[0]-$tday[0]); + my $rete = ($eday[2]-$tday[2])*365+($eday[1]-$tday[1])*31+($eday[0]-$tday[0]); + if( ($rete>=0) && ($rets<=0) ){ + $todaydesc = $chunks[5]; + $todaytype = "vacation"; + Log3 $name, 1,"[YAAHM] found today=vacation \"$todaydesc\" in calendar $vacdayDev"; + } + $rets = ($sday[2]-$tmor[2])*365+($sday[1]-$tmor[1])*31+($sday[0]-$tmor[0]); + $rete = ($eday[2]-$tmor[2])*365+($eday[1]-$tmor[1])*31+($eday[0]-$tmor[0]); + if( ($rete>=0) && ($rets<=0) ){ + $tomdesc = $chunks[5]; + $tomtype = "vacation"; + Log3 $name, 1,"[YAAHM] found tomorrow=vacation \"$tomdesc\" in calendar $vacdayDev"; + } + } + } + }else{ + Log3 $name, 1,"[YAAHM] unknown vacation device $vacdayDev"; + } + } + #-- put into readings + if( $todaytype eq "vacation" ){ + $hash->{DATA}{"DD"}[0]{"daytype"} = "vacation"; + $hash->{DATA}{"DD"}[0]{"desc"} = $todaydesc; + }else{ + } + if( $tomtype eq "vacation" ){ + $hash->{DATA}{"DD"}[1]{"daytype"} = "vacation"; + $hash->{DATA}{"DD"}[1]{"desc"} = $tomdesc; + }else{ + } + + #-- weekend has higher priority + if( strftime('%u', localtime(time)) > 5){ + $todaytype = "weekend"; + if( $hash->{DATA}{"DD"}[0]{"daytype"} ne "workday" ){ + $hash->{DATA}{"DD"}[0]{"desc"} = $yaahm_tt->{"weekend"}.", ".$hash->{DATA}{"DD"}[0]{"desc"}; + }else{ + $hash->{DATA}{"DD"}[0]{"desc"} = $yaahm_tt->{"weekend"}; + } + $hash->{DATA}{"DD"}[0]{"daytype"} = "weekend"; + } + + if( strftime('%u', localtime(time+86400)) > 5){ + $tomtype = "weekend"; + if( $hash->{DATA}{"DD"}[1]{"daytype"} ne "workday" ){ + $hash->{DATA}{"DD"}[1]{"desc"} = $yaahm_tt->{"weekend"}.", ".$hash->{DATA}{"DD"}[1]{"desc"}; + }else{ + $hash->{DATA}{"DD"}[1]{"desc"} = $yaahm_tt->{"weekend"}; + } + $hash->{DATA}{"DD"}[1]{"daytype"} = "weekend"; + } + + #-- holidays have the highest priority + my $holidayDevs = AttrVal( $name, "holidayDevices", "" ); + foreach my $holidayDev ( split( /,/, $holidayDevs ) ) { + + #-- device of type holiday + if( IsDevice( $holidayDev, "holiday" )){ + $stoday = strftime('%2m-%2d', localtime(time)); + $stom = strftime('%2m-%2d', localtime(time+86400)); + my $tod = holiday_refresh( $holidayDev, $stoday ); + if ( $tod ne "none" ) { + $todaydesc = $tod; + $todaytype = "holiday"; + Log3 $name, 1,"[YAAHM] found today=holiday \"$todaydesc\" in holiday $holidayDev"; + } + $tod = holiday_refresh( $holidayDev, $stom ); + if ( $tod ne "none" ) { + $tomdesc = $tod; + $tomtype = "holiday"; + Log3 $name, 1,"[YAAHM] found tomorrow=holiday \"$tomdesc\" in holiday $holidayDev"; + } + + #-- device of type calendar + }elsif( IsDevice($holidayDev, "Calendar" )){ + $stoday = strftime('%2d.%2m.%2Y', localtime(time)); + $stom = strftime('%2d.%2m.%2Y', localtime(time+86400)); + $line=Calendar_Get($defs{$holidayDev},"get","text","mode=alarm|start|upcoming"); + if($line){ + chomp($line); + @lines = split('\n',$line); + foreach $line (@lines){ + chomp($line); + $date = substr($line,0,8); + if( $date eq $stoday ){ + $todaydesc = substr($line,15); + $todaytype = "holiday"; + Log3 $name, 1,"[YAAHM] found today=holiday \"$todaydesc\" in calendar $holidayDev"; + } + if( $date eq $stom ){ + $tomdesc = substr($line,15); + $tomtype = "holiday"; + Log3 $name, 1,"[YAAHM] found tomorrow=holiday \"$tomdesc\" in calendar $holidayDev"; + } + } + } + }else{ + Log3 $name, 1,"[YAAHM] unknown holiday device $holidayDev"; + } + } + + #-- put into store + if( $todaytype eq "holiday" ){ + if( $hash->{DATA}{"DD"}[0]{"daytype"} ne "workday" ){ + $hash->{DATA}{"DD"}[0]{"desc"} = $todaydesc.", ".$hash->{DATA}{"DD"}[0]{"desc"}; + }else{ + $hash->{DATA}{"DD"}[0]{"desc"} = $todaydesc; + } + $hash->{DATA}{"DD"}[0]{"daytype"} = "holiday"; + } + if( $tomtype eq "holiday" ){ + if( $hash->{DATA}{"DD"}[1]{"daytype"} ne "workday" ){ + $hash->{DATA}{"DD"}[1]{"desc"} = $tomdesc.", ".$hash->{DATA}{"DD"}[1]{"desc"}; + }else{ + $hash->{DATA}{"DD"}[1]{"desc"} = $tomdesc; + } + $hash->{DATA}{"DD"}[1]{"daytype"} = "holiday"; + } + + #-- sunrise and sunset today and tomorrow + my ($sttoday,$sttom); + my ($strise0,$stset0,$stseas0,$strise1,$stset1,$stseas1); + + #-- function calls to 95_Astro.pm module ! + $sttoday = strftime('%4Y-%2m-%2d', localtime(time)); + $strise0 = Astro_Get($hash,"dummy","text", "SunRise",$sttoday).":00"; + $stset0 = Astro_Get($hash,"dummy","text", "SunSet",$sttoday).":00"; + $stseas0 = Astro_Get($hash,"dummy","text", "ObsSeasonN",$sttoday); + + $sttom = strftime('%4Y-%2m-%2d', localtime(time+86400)); + $strise1 = Astro_Get($hash,"dummy","text", "SunRise",$sttom).":00"; + $stset1 = Astro_Get($hash,"dummy","text", "SunSet",$sttom).":00"; + $stseas1 = Astro_Get($hash,"dummy","text", "ObsSeasonN",$sttom); + + my ($hour,$min) = split(":",$stset0); + $hash->{DATA}{"DD"}[0]{"sunset"} = sprintf("%02d:%02d",$hour,$min); + + ($hour,$min) = split(":",$stset1); + $hash->{DATA}{"DD"}[1]{"sunset"} = sprintf("%02d:%02d",$hour,$min); + + ($hour,$min) = split(":",$strise0); + $hash->{DATA}{"DD"}[0]{"sunrise"} = sprintf("%02d:%02d",$hour,$min); + + ($hour,$min) = split(":",$strise1); + $hash->{DATA}{"DD"}[1]{"sunrise"} = sprintf("%02d:%02d",$hour,$min); + + #-- check season + $hash->{DATA}{"DD"}[0]{"season"} = $seasons[$stseas0]; + $hash->{DATA}{"DD"}[1]{"season"} = $seasons[$stseas1]; + + #-- + readingsBeginUpdate($hash); + readingsBulkUpdateIfChanged( $hash, "todayType",$todaytype ); + readingsBulkUpdateIfChanged( $hash, "tr_todayType",$yaahm_tt->{$hash->{DATA}{"DD"}[0]{"daytype"}} ); + if( $todaytype eq "workday"){ + readingsBulkUpdateIfChanged( $hash, "todayDesc","--" ) + }elsif( $todaytype eq "vacation"){ + readingsBulkUpdateIfChanged( $hash, "todayDesc",$hash->{DATA}{"DD"}[0]{"desc"} ) + }elsif( $todaytype eq "weekend"){ + readingsBulkUpdateIfChanged( $hash, "todayDesc","--" ) + }else{ + readingsBulkUpdateIfChanged( $hash, "todayDesc",$hash->{DATA}{"DD"}[0]{"desc"} ) + } + readingsBulkUpdateIfChanged( $hash, "tomorrowType",$tomtype ); + readingsBulkUpdateIfChanged( $hash, "tr_tomorrowType",$yaahm_tt->{$hash->{DATA}{"DD"}[1]{"daytype"}} ); + if( $tomtype eq "workday"){ + readingsBulkUpdateIfChanged( $hash, "tomorrowDesc","--" ) + }elsif( $tomtype eq "vacation"){ + readingsBulkUpdateIfChanged( $hash, "tomorrowDesc",$hash->{DATA}{"DD"}[1]{"desc"} ) + }elsif( $tomtype eq "weekend"){ + readingsBulkUpdateIfChanged( $hash, "tomorrowDesc","--" ) + }else{ + readingsBulkUpdateIfChanged( $hash, "tomorrowDesc",$hash->{DATA}{"DD"}[1]{"desc"} ) + } + + YAAHM_setWeeklyTime($hash); + + readingsEndUpdate($hash,1); + return undef; + +} + +######################################################################################### +# +# YAAHM_Sun - obtains times for sunrise and sunset +# +# Parameter hash = hash of device addressed +# +######################################################################################### + +sub YAAHM_Sun($) { + my ($hash) = @_; + + #-- sunrise + my $st = $hash->{DATA}{"DD"}[0]{"sunrise"}; + + my ($hour,$min) = split(":",$st); + $hash->{DATA}{"DT"}{"sunrise"}[0] = sprintf("%02d:%02d",$hour,$min); + + #-- before sunrise + my ($ofh,$ofm); + my $of = $hash->{DATA}{"DT"}{"beforesunrise"}[1]; + if( $of !~ /\d\d:\d\d/){ + Log 1,"[YAAHM] Offset before sunrise not in format hh:mm, using 00:00"; + $ofh = 0; + $ofm = 0; + }else{ + ($ofh,$ofm) = split(":",$of); + } + $ofm = $min-$ofm; + $ofh = $hour-$ofh; + if( $ofm < 0 ){ + $ofh--; + $ofm +=60; + } + $hash->{DATA}{"DT"}{"beforesunrise"}[0] = sprintf("%02d:%02d",$ofh,$ofm); + + #-- after sunrise + $of = $hash->{DATA}{"DT"}{"aftersunrise"}[1]; + if( $of !~ /\d\d:\d\d/){ + Log 1,"[YAAHM] Offset after sunrise not in format hh:mm, using 00:00"; + $ofh = 0; + $ofm = 0; + }else{ + ($ofh,$ofm) = split(":",$of); + } + $ofm = $min+$ofm; + $ofh = $hour+$ofh; + if( $ofm > 59 ){ + $ofh++; + $ofm -=60; + } + $hash->{DATA}{"DT"}{"aftersunrise"}[0] = sprintf("%02d:%02d",$ofh,$ofm); + + #-- sunset + $st = $hash->{DATA}{"DD"}[0]{"sunset"}; + ($hour,$min) = split(":",$st); + $hash->{DATA}{"DT"}{"sunset"}[0] = sprintf("%02d:%02d",$hour,$min); + + #-- before sunset + $of = $hash->{DATA}{"DT"}{"beforesunset"}[1]; + if( $of !~ /\d\d:\d\d/){ + Log 1,"[YAAHM] Offset before sunset not in format hh:mm, using 00:00"; + $ofh = 0; + $ofm = 0; + }else{ + ($ofh,$ofm) = split(":",$of); + } + $ofm = $min-$ofm; + $ofh = $hour-$ofh; + if( $ofm < 0 ){ + $ofh--; + $ofm +=60; + } + $hash->{DATA}{"DT"}{"beforesunset"}[0] = sprintf("%02d:%02d",$ofh,$ofm); + + #-- after sunset + $of = $hash->{DATA}{"DT"}{"aftersunset"}[1]; + if( $of !~ /\d\d:\d\d/){ + Log 1,"[YAAHM] Offset after sunset not in format hh:mm, using 00:00"; + $ofh = 0; + $ofm = 0; + }else{ + ($ofh,$ofm) = split(":",$of); + } + $ofm = $min+$ofm; + $ofh = $hour+$ofh; + if( $ofm > 59 ){ + $ofh++; + $ofm -=60; + } + $hash->{DATA}{"DT"}{"aftersunset"}[0] = sprintf("%02d:%02d",$ofh,$ofm); + + #-- before midnight + $hour = 24; + $min = 0; + $of = $hash->{DATA}{"DT"}{"beforemidnight"}[1]; + if( $of !~ /\d\d:\d\d/){ + Log 1,"[YAAHM] Offset before midnight not in format hh:mm, using 00:05"; + $ofh = 0; + $ofm = 5; + }else{ + ($ofh,$ofm) = split(":",$of); + } + $ofm = $min-$ofm; + $ofh = $hour-$ofh; + if( $ofm < 0 ){ + $ofh--; + $ofm +=60; + } + $hash->{DATA}{"DT"}{"beforemidnight"}[0] = sprintf("%02d:%02d",$ofh,$ofm); + + #-- after midnight + $hour = 0; + $min = 0; + $of = $hash->{DATA}{"DT"}{"aftermidnight"}[1]; + if( $of !~ /\d\d:\d\d/){ + Log 1,"[YAAHM] Offset after midnight not in format hh:mm, using 00:05"; + $ofh = 0; + $ofm = 5; + }else{ + ($ofh,$ofm) = split(":",$of); + } + $ofm = $min+$ofm; + $ofh = $hour+$ofh; + if( $min > 59 ){ + $ofh++; + $ofm -=60; + } + $hash->{DATA}{"DT"}{"aftermidnight"}[0] = sprintf("%02d:%02d",$ofh,$ofm); +} + +######################################################################################### +# +# YAAHM_timewidget - returns SVG code for inclusion into any room page +# +# Parameter name = name of the YAAHM definition +# +######################################################################################### + +sub YAAHM_timewidget($){ + my ($arg) = @_; + my $name = $FW_webArgs{name}; + $name =~ s/'//g; + + my @size=split('x',($FW_webArgs{size} ? $FW_webArgs{size} : '400x400')); + #Log 1,"++++++++++++++++++++++++++++++++++++++++++++"; + #Log 1,"YAAHM_timewidget type $type (subtype $subtype) called with $arg"; + #Log 1,"YAAHM_timewidget has size ".$size[0]."x".$size[1]; + #Log 1,"++++++++++++++++++++++++++++++++++++++++++++"; + + $FW_RETTYPE = "image/svg+xml"; + $FW_RET=""; + FW_pO ''; + + my $hash = $defs{$name}; + # Midnight = 0 200 + # Noon = 0 -200 + # hh:mm => a = (hh*60 + mm)/1140 + + my $radius = 250; + + my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time); + my $t_now = sprintf("%02d:%02d",$hour,$min); + my $a_now = (60*$hour + $min)/1440 * 2 * pi; + my $x_now = -int(sin($a_now)*$radius*10)/10; + my $y_now = int(cos($a_now)*$radius*10)/10; + + my $t_sunrise = defined($hash->{DATA}{"DD"}[0]{"sunrise"}) ? $hash->{DATA}{"DD"}[0]{"sunrise"} : "06:00"; + $t_sunrise =~ s/^0//; + ($hour,$min) = split(":",$t_sunrise); + my $a_sunrise = (60*$hour + $min)/1440 * 2 * pi; + my $x_sunrise = -int(sin($a_sunrise)*$radius*10)/10; + my $y_sunrise = int(cos($a_sunrise)*$radius*10)/10; + + my $t_morning = defined($hash->{DATA}{"DT"}{"morning"}[0]) ? $hash->{DATA}{"DT"}{"morning"}[0] : "08:00"; + $t_morning =~ s/^0//; + ($hour,$min) = split(":",$t_morning); + my $a_morning = (60*$hour + $min)/1440 * 2 * pi; + my $x_morning = -int(sin($a_morning)*$radius*10)/10; + my $y_morning = int(cos($a_morning)*$radius*10)/10; + + my $t_noon = defined($hash->{DATA}{"DT"}{"noon"}[0]) ? $hash->{DATA}{"DT"}{"noon"}[0] : "12:00"; + $t_noon =~ s/^0//; + ($hour,$min) = split(":",$t_noon); + my $a_noon = (60*$hour + $min)/1440 * 2 * pi; + my $x_noon = -int(sin($a_noon)*$radius*10)/10; + my $y_noon = int(cos($a_noon)*$radius*10)/10; + + my $t_afternoon = defined($hash->{DATA}{"DT"}{"afternoon"}[0]) ? $hash->{DATA}{"DT"}{"afternoon"}[0] : "14:00"; + $t_afternoon =~ s/^0//; + ($hour,$min) = split(":",$t_afternoon); + my $a_afternoon = (60*$hour + $min)/1440 * 2 * pi; + my $x_afternoon = -int(sin($a_afternoon)*$radius*10)/10; + my $y_afternoon = int(cos($a_afternoon)*$radius*10)/10; + + my $t_sunset = defined($hash->{DATA}{"DD"}[0]{"sunset"}) ? $hash->{DATA}{"DD"}[0]{"sunset"} : "18:00"; + $t_sunset =~ s/^0//; + ($hour,$min) = split(":",$t_sunset); + my $a_sunset = (60*$hour + $min)/1440 * 2 * pi; + my $x_sunset = -int(sin($a_sunset)*$radius*10)/10; + my $y_sunset = int(cos($a_sunset)*$radius*10)/10; + + my $t_evening = defined($hash->{DATA}{"DT"}{"evening"}[0]) ? $hash->{DATA}{"DT"}{"evening"}[0] : "19:00"; + $t_evening =~ s/^0//; + ($hour,$min) = split(":",$t_evening); + my $a_evening = (60*$hour + $min)/1440 * 2 * pi; + my $x_evening = -int(sin($a_evening)*$radius*10)/10; + my $y_evening = int(cos($a_evening)*$radius*10)/10; + + my $t_night = defined($hash->{DATA}{"DT"}{"night"}[0]) ? $hash->{DATA}{"DT"}{"night"}[0] : "22:00"; + $t_night =~ s/^0//; + ($hour,$min) = split(":",$t_night); + my $a_night = (60*$hour + $min)/1440 * 2 * pi; + my $x_night = -int(sin($a_night)*$radius*10)/10; + my $y_night = int(cos($a_night)*$radius*10)/10; + FW_pO ''. + sprintf('',int(-$x_noon/$radius*100),int(-$y_noon/$radius*100)). + ''. + ''. + ''. + sprintf('',int(-$x_noon/$radius*100),int(-$y_noon/$radius*100)). + ''. + ''. + ''. + sprintf('',int(-$x_noon/$radius*100),int(-$y_noon/$radius*100)). + ''. + ''. + ''. + ''; + + FW_pO ''; + + #-- daytime arc + FW_pO ' '; + + #-- sunset to sunrise sector + FW_pO ''; + + #-- sunrise to morning sector + FW_pO ''; + + #-- morning to evening sector + FW_pO ''; + + #-- evening to sunset sector + FW_pO ''; + + #-- midnight line + FW_pO ''; + FW_pO '0:00'; + + #--sunrise line + FW_pO ''; + FW_pO ''.$t_sunrise.''; + + #--morning line + FW_pO ''; + FW_pO ''.$t_morning.''; + + #--noon line + FW_pO ''; + FW_pO ''.$t_noon.''; + + #--afternoon line + FW_pO ''; + FW_pO ''.$t_afternoon.''; + + #--sunset line + FW_pO ''; + FW_pO ''.$t_sunset.''; + + #--evening line + FW_pO ''; + FW_pO ''.$t_evening.''; + + #--night line + FW_pO ''; + FW_pO ''.$t_night.''; + + #--now line + FW_pO ''; + FW_pO ''.$t_now.''; + + + FW_pO ''; + FW_pO ''; + return ($FW_RETTYPE, $FW_RET); +} + +######################################################################################### +# +# YAAHM_toptable - returns incomplete HTML code for inclusion into any room page +# (action and overview fields) +# +# Parameter name = name of the YAAHM definition +# +######################################################################################### + +sub YAAHM_toptable($){ + my ($name) = @_; + + my $ret = ""; + + my $hash = $defs{$name}; + + if( !defined($yaahm_tt) ){ + #-- readjust language + my $lang = AttrVal("global","language","EN"); + if( $lang eq "DE"){ + $yaahm_tt = \%yaahm_transtable_DE; + }else{ + $yaahm_tt = \%yaahm_transtable_EN; + } + } + + #-- something's rotten in the state of denmark + my $st = $hash->{DATA}{"DD"}[0]{"sunrise"}; + if( !defined($st) || $st eq "00:00" ){ + YAAHM_GetDayStatus($hash); + } + YAAHM_Sun($hash); + + #-- + my $lockstate = ($hash->{READINGS}{lockstate}{VAL}) ? $hash->{READINGS}{lockstate}{VAL} : "unlocked"; + my $showhelper = ($lockstate eq "unlocked") ? 1 : 0; + + %dailytable = %{$hash->{DATA}{"DT"}}; + my $dailyno = scalar keys %dailytable; + my $weeklyno = int( @{$hash->{DATA}{"WT"}} ); + + #-- + $ret .= "\n"; + + $ret .= "\n"; + $ret .= "\n"; + ### action ################################################################################################ + #-- determine columns + my $cols = max(int(@modes),int(@states),$weeklyno); + $ret .= ""; + + $ret .= "\n"; + ### daily overview ################################################################################################ + $ret .= "\n"; + + return $ret; + + } + +######################################################################################### +# +# YAAHM_Shorttable - returns complete HTML code for inclusion into any room page +# (action and overview fields) +# +# Parameter name = name of the YAAHM definition +# +######################################################################################### + +sub YAAHM_Shorttable($){ + my ($name) = @_; + + my $ret = YAAHM_toptable($name); + + #-- complete the code of the page + $ret .= "
".$yaahm_tt->{"action"}."
". + "". + ""; + for( my $i=0; $i<$cols; $i++){ + if( $i < int(@modes)){ + $ret .= ""; + }else{ + $ret .= ""; + } + } + $ret .= ""; + $ret .= "". + ""; + for( my $i=0; $i<$cols; $i++){ + if( $i < int(@states)){ + $ret .= ""; + }else{ + $ret .= ""; + } + } + $ret .= ""; + + #-- repeat manual next for every weekly table + my $nval = ""; + my $wupn; + $ret .= ""; + for (my $i=0;$i<$weeklyno;$i++){ + $wupn = $hash->{DATA}{"WT"}[$i]{"name"}; + $nval = ( defined($hash->{DATA}{"WT"}[$i]{"next"}) ) ? $hash->{DATA}{"WT"}[$i]{"next"} : ""; + $ret .= sprintf("",$i,$i); + } + $ret .= "\n"; + $ret .= "
".$yaahm_tt->{"mode"}."
".ReadingsVal($name,"tr_housemode",undef)."
{$modes[$i]}. + "\" style=\"height:20px; width:120px;\" onclick=\"javascript:yaahm_mode('$name','".$modes[$i]."')\"/>
".$yaahm_tt->{"state"}."
".ReadingsVal($name,"tr_housestate",undef). + "
".ReadingsVal($name,"sym_housestate",undef)."
{$states[$i]}. + "\" style=\"height:20px; width:120px;\" onclick=\"javascript:yaahm_state('$name','".$states[$i]."')\"/>
".$yaahm_tt->{"manual"}."$wupn
".$yaahm_tt->{"overview"}."
"; + #-- time widget here + $ret .= ""; + #-- continue table + $ret .= "\n"; + $ret .= "\n"; + $ret .= "\n"; + $ret .= "\n"; + $ret .= "\n"; + $ret .= "\n"; + $ret .= "\n"; + $ret .= "\n"; + + #-- weekly timers + my $ts; + for( my $i=0;$i<$weeklyno;$i++ ){ + $wupn = $hash->{DATA}{"WT"}[$i]{"name"}; + + #-- timer status + if( defined($defs{$name.".wtimer_".$i.".IF"})){ + if( ReadingsVal($name.".wtimer_".$i.".IF","mode","") ne "disabled" ){ + $ts = "
"; + }else{ + $ts = "
"; + } + }else{ + $ts = ""; + } + + $ret .= ""; + if( $i == 0){ + $ret .= ""; + }else{ + $ret .= ""; + } + $ret.="". + "". + "\n"; + } + $ret .= "
"."".$yaahm_tt->{"today"}. + "".$yaahm_tt->{"tomorrow"}. + "
".ReadingsVal($name,"tr_housestate",undef)."
⟶". + $yaahm_tt->{"secstate"}."
".$yaahm_tt->{$weeklytable[$hash->{DATA}{"DD"}[0]{"weekday"}]}[0] . + "".$yaahm_tt->{$weeklytable[$hash->{DATA}{"DD"}[1]{"weekday"}]}[0]. + "
".ReadingsVal($name,"sdev_housestate","")."
".$hash->{DATA}{"DD"}[0]{"date"}. + "".$hash->{DATA}{"DD"}[1]{"date"}."
".$yaahm_tt->{"daylight"}."".$hash->{DATA}{"DD"}[0]{"sunrise"}."-".$hash->{DATA}{"DD"}[0]{"sunset"}. + "".$hash->{DATA}{"DD"}[1]{"sunrise"}."-".$hash->{DATA}{"DD"}[1]{"sunset"}."
".$yaahm_tt->{"daytime"}."".$hash->{DATA}{"DT"}{"morning"}[0]."-". + $hash->{DATA}{"DT"}{"night"}[0]."
".$yaahm_tt->{"daytype"}."".$yaahm_tt->{$hash->{DATA}{"DD"}[0]{"daytype"}}. + "".$yaahm_tt->{$hash->{DATA}{"DD"}[1]{"daytype"}}."
".$yaahm_tt->{"description"}."".$hash->{DATA}{"DD"}[0]{"desc"}. + "".$hash->{DATA}{"DD"}[1]{"desc"}."
".$yaahm_tt->{"date"}."".$hash->{DATA}{"DD"}[0]{"special"}. + "".$hash->{DATA}{"DD"}[1]{"special"}."
". + "". + "".$wupn. + "$ts
".ReadingsVal($name,"ring_$i","")."
".ReadingsVal($name,"ring_".$i."_1","")."
"; + InternalTimer(gettimeofday()+ 1, "YAAHM_informer", $defs{$FW_cname},0); + return $ret; +} + +######################################################################################### +# +# YAAHM_Longtable - returns complete HTML code for the full YAAHM page +# (action, overview, daily and weekly profile) +# +# Parameter name = name of the YAAHM definition +# +######################################################################################### + +sub YAAHM_Longtable($){ + my ($name) = @_; + + my $ret = ""; + + my $hash = $defs{$name}; + my $id = $defs{$name}{NR}; + + #-- + my $lockstate = ($hash->{READINGS}{lockstate}{VAL}) ? $hash->{READINGS}{lockstate}{VAL} : "unlocked"; + my $showhelper = ($lockstate eq "unlocked") ? 1 : 0; + + %dailytable = %{$hash->{DATA}{"DT"}}; + my $dailyno = scalar keys %dailytable; + my $weeklyno = int( @{$hash->{DATA}{"WT"}} ); + + #-- + $ret = YAAHM_toptable($name); + + ### daily profile table ################################################################################################ + my $row = 1; + my $event = ""; + my $sval = ""; + my $eval = ""; + my $xval = ""; + + my $dh = (defined($attr{$name}{"timeHelper"})) ? $attr{$name}{"timeHelper"} : undef; + + #-- global status of timer + my ($tl,$ts); + if( defined($defs{$name.".dtimer.IF"})){ + $tl = "$name.dtimer.IF"; + #-- green hook + if( ReadingsVal($name.".dtimer.IF","mode","") ne "disabled" ){ + $ts = "✓\n"; + #-- red cross + }else{ + $ts = "❌\n"; + } + }else{ + $tl= $yaahm_tt->{"notstarted"}; + $ts =""; + } + + #-- name link button status + $ret .= "
".$yaahm_tt->{"daily"}.$yaahm_tt->{"profile"}."\n". + "$ts"; + $ret .= "\n"; + $ret .= "
$tl
{"start"}." ".$yaahm_tt->{"daily"}.$yaahm_tt->{"timer"}."\" onclick=\"javascript:yaahm_startDayTimer('$name')\"/>
"; + + #-- header line + $ret .= "\n"; + $ret .= "". + "". + "". + "\n"; + + #-- iterate through table + foreach my $key (sort YAAHM_dsort keys %dailytable){ + $row++; + $event= $yaahm_tt->{$key}; + $sval = $dailytable{$key}[0]; + $eval = $dailytable{$key}[1]; + $xval = $dailytable{$key}[2]; + $xval = "" if( !defined($xval) ); + #-- + if( $dh ){ + #-- timeHelper not in command list + if( $xval !~ /^{$dh/ ){ + if( defined($xval) && length($xval)>0 ){ + $xval ="{".$dh."('".$key."')},".$xval; + }else{ + $xval ="{".$dh."('".$key."')}"; + } + } + } + + $ret .= sprintf("\n", ($row&1)?"odd":"even"); + #-- First field + #-- Only reference for wakeup + if( $key =~ /^wakeup.*/ ){ + $ret .= "\n"; + next; + #-- Only reference for sleep + }elsif( $key =~ /^sleep.*/ ){ + $ret .= "\n"; + next; + #-- calculated value for sunrise/sunset + }elsif( $key =~ /.*((sunrise)|(sunset)|(midnight)).*/ ){ + my $pre; + if( $key =~ /.*sunrise.*/ ){ + $pre = $hash->{DATA}{"DD"}[0]{"sunrise"} + }elsif( $key =~ /.*sunset.*/ ){ + $pre = $hash->{DATA}{"DD"}[0]{"sunset"} + }else{ + $pre = "00:00"; + } + $ret .= "" + }elsif( $key =~ /.*after.*/ ){ + $ret .= "$pre +   = $sval    " + }else{ + $ret .= "$pre    " + } + #-- normal input of one time + }else{ + $ret .= ""; + } + #-- Second field + $ret .= "\n"; + } + $ret .= "
".$yaahm_tt->{"event"}."". + $yaahm_tt->{"time"}." [hh:mm]   ".$yaahm_tt->{"action"}."
Start/Offset   End   
$event".$yaahm_tt->{"weekly"}.$yaahm_tt->{"profile"}."
".$yaahm_tt->{"weekly"}.$yaahm_tt->{"profile"}."
"; + if( $key =~ /.*before.*/ ){ + $ret .= "$pre -   = $sval    
"; + + ### weekly profile table ################################################################################################ + $row = 1; + $event = ""; + $sval = ""; + + my $wupn; + my $wt = ( $weeklyno == 1) ? $yaahm_tt->{"profile"} :$yaahm_tt->{"profiles"}; + + $ret .= "
".$yaahm_tt->{"weekly"}.$wt. + "  {"start"}." ".$yaahm_tt->{"weekly"}.$yaahm_tt->{"timer"}."\" onclick=\"javascript:yaahm_startWeeklyTimer('$name')\"/>
"; + + $ret .= "\n"; + + #-- repeat name for every weekly table + $ret .= ""; + for (my $i=0;$i<$weeklyno;$i++){ + $wupn = $hash->{DATA}{"WT"}[$i]{"name"}; + $ret .= ""; + } + $ret .= "\n"; + + #-- repeat link for every weekly table + $ret .= ""; + + #-- array with activity status + my @tss; + + for (my $i=0;$i<$weeklyno;$i++){ + $wupn = $hash->{DATA}{"WT"}[$i]{"name"}; + #-- timer status + if( defined($defs{$name.".wtimer_".$i.".IF"})){ + $tl = "".$name.".wtimer_".$i.".IF"; + if( ReadingsVal($name.".wtimer_".$i.".IF","mode","") ne "disabled" ){ + push(@tss,"
"); + }else{ + push(@tss,"
"); + } + }else{ + $tl = $yaahm_tt->{"notstarted"}; + push(@tss,""); + } + $ret .= sprintf("",$i,$tl); + } + $ret .= "\n"; + + #-- repeat active status for every weekly table + my $asg = ""; + my $ast = ""; + my $ass; + my $acc; + #--header + for(my $i=0;$i{$profmode[$i]},0,3)." "; + $ast .= $yaahm_tt->{$profmode[$i]}." "; + } + for(my $i=0;$i{$profday[$i]},0,3)." "; + $ast .= $yaahm_tt->{$profday[$i]}." "; + } + $ret .= ""; + for (my $i=0;$i<$weeklyno;$i++){ + $wupn = $hash->{DATA}{"WT"}[$i]{"name"}; + $ret .= ""; + } + $ret .= "\n"; + + #-- repeat action for every weekly table + $ret .= ""; + for (my $i=0;$i<$weeklyno;$i++){ + $xval = $hash->{DATA}{"WT"}[$i]{"action"}; + #-- + if( $dh && $i<2 ){ + #-- timeHelper not in command list + $wupn = ($i==0) ? "wakeup" : "sleep"; + if( $xval !~ /^{$dh/ ){ + if( defined($xval) && length($xval)>0 ){ + $xval ="{".$dh."('".$wupn."')},".$xval; + }else{ + $xval ="{".$dh."('".$wupn."')}"; + } + } + } + $ret .= sprintf("",$i); + } + $ret .= "\n"; + + #-- repeat unit for every weekly table + $ret .= ""; + for (my $i=0;$i<$weeklyno;$i++){ + $ret .= ""; + } + $ret .= "\n"; + + #-- weekday header + $ret .= ""; + for (my $i=0;$i<$weeklyno;$i++){ + $ret .= ""; + } + $ret .= "\n"; + + for (my $j=0;$j<7;$j++){ + my $key = $weeklytable[$j]; + $row++; + $event = $yaahm_tt->{$key}[0]; + + $ret .= sprintf("\n", ($row&1)?"odd":"even"); + for (my $i=0;$i<$weeklyno;$i++){ + $sval = $hash->{DATA}{"WT"}[$i]{$key}; + $ret .= sprintf("",$key,$i); + } + $ret .= "\n"; + } + $ret .= "
".$yaahm_tt->{"name"}."$wupn
".$yaahm_tt->{"timer"}."
%s
".$yaahm_tt->{"active"}."
".$asg."
".$tss[$i]."
"; + + $asg = ""; + $ass = ( defined($hash->{DATA}{"WT"}[$i]{"acti_m"}) ) ? $hash->{DATA}{"WT"}[$i]{"acti_m"} : ""; + for( my $j=0;$j ",$i); + } + $ass = ( defined($hash->{DATA}{"WT"}[$i]{"acti_d"}) ) ? $hash->{DATA}{"WT"}[$i]{"acti_d"} : ""; + for( my $j=0;$j ",$i); + } + $ret .= "$asg
".$yaahm_tt->{"action"}."
".$yaahm_tt->{"time"}." [hh:mm]
".$yaahm_tt->{"weekday"}."
$event
"; + + #-- complete the code of the page + $ret .= ""; + #InternalTimer(gettimeofday()+ 3, "YAAHM_informer", $defs{$FW_cname},0); + + return $ret; +} + +1; + +=pod +=item helper +=item summary to set up a cyclic processing of commands +=item summary_DE dient dazu, eine zyklische Abarbeitung von Kommandos zu realisieren +=begin html + + +

YAAHM

+

Yet Another Auto Home Module to set up a cyclic processing of commands (daily, weekly, monthly, yearly profile). See German Wiki page at YAAHM

+ +

Define

+

+ define <name> YAAHM +
Defines the YAAHM system.

+ + Notes:
    +
  • This module uses the global attribute language to determine its output data
    + (default: EN=english). For German output set attr global language DE.
  • +
+

Set

+
    +
  • + set <name> time <timeevent>
    + Set the current house time (event), i.e. one of several values: +
      +
    • (after|before) midnight | [before|after] sunrise | [before|after] sunset are calculated from astronomical data (±offset). + These values vary from day to day, only the offset can be specified in the daily profile.
    • +
    • morning | noon | afternoon | evening | night are fixed time events specified in the daily profile. + The time phase between events morning and night is called daytime, the + time phase between events night and morning is called nighttime
    • +
    • wakeup|sleep are time events specified in the weekly default profiles Wakeup and Sleep, i.e. the value may change from day to day.
    • +
    + The actual changes to certain devices are made by the functions in the command field, or by an external timeHelper function. +
  • +
  • + set <name> manualnext <timernumber> <time>
    + set <name> manualnext <timername> <time>
    + For the weekly timer identified by its number (starting at 0) or its name, set the next ring time manually. The time specification <time>must be in the format hh:mm or "off" + If the time specification <time> is later than the current time, it will be used for today. If it is earlier than the current time, it will be used tomorrow. +
  • +
  • + set <name> mode normal | party | absence + +
    Set the current house mode, i.e. one of several values: +
      +
    • normal - normal daily and weekly time profiles apply
    • +
    • party - can be used in the timeHelper function to suppress certain actions, like e.g. those that set the house (security) state to secured or the house time event to night.
    • +
    • absence - can be used in the timeHelper function to suppress certain actions. Valid until manual mode change
    • +
    + House modes are valid until manual mode change. If the attribute modeAuto is set (see below), mode will change automatically at certain time events. + The actual changes to certain devices are made by an external modeHelper function. +
  • +
  • + set <name> state unsecured | secured | protected | guarded + +
    Set house (security) state, i.e. one of several values: +
      +
    • unsecured - Example: doors etc. +
    • +
    • secured - Example: doors etc. are locked, windows may not be open +
    • +
    • protected - Example: doors etc. are locked, windows may not be open, alarm system is armed +
    • +
    • guarded - Example: doors etc. are locked, windows may not be open, alarm is armed, a periodic house check is run and a simulation as well +
    • +
    + House (security) states are valid until manual change. If the attribute stateAuto is set (see below), state will change automatically at certain times. + The actual changes to certain devices are made by an external stateHelper function. If these external devices are in their proper state + for a particular house (security) state can be checked automatically, see the attribute stateDevices +
  • + +
  • + set <name> createWeekly <string> + +
    Create a new weekly profile <string>
  • +
  • + set <name> deleteWeekly <string> + +
    Delete the weekly profile <string>
  • +
  • + set <name> locked|unlocked + +
    Set the lockstate of the yaahm module to locked (i.e., yaahm setups + may not be changed) resp. unlocked (i.e., yaahm setups may be changed>)
  • +
+ +

Get

+
    +
  • + get <name> version +
    Display the version of the module
  • +
  • + get <name> template +
    Return an (empty) perl subroutine for the helper functions
  • +
+ +

Attributes

+ +=end html +=begin html_DE + + +

YAAHM

+Deutsche Dokumentation im Wiki vorhanden, die englische Version gibt es hier: YAAHM +=end html_DE +=cut diff --git a/fhem/www/pgm2/yaahm.js b/fhem/www/pgm2/yaahm.js new file mode 100644 index 000000000..2beecfc10 --- /dev/null +++ b/fhem/www/pgm2/yaahm.js @@ -0,0 +1,205 @@ +//######################################################################################## +// yaahm.js +// Version 1.0 +// See 95_YAAHM for licensing +//######################################################################################## +//# Prof. Dr. Peter A. Henning + +function encodeParm(oldval) { + var newval; + newval = oldval.replace(/\+/g, '%2B'); + newval = newval.replace(/#/g, '%23'); + newval = newval.replace(/"/g, '%27'); + return newval; +} + +// Tool Tips +// $( function() { +// $( document ).tooltip(); +// } ); + +// Expand Text box + $(function () { + $(".expand").focus(function () { + $(this).animate({ + width: '200px' + }, + "slow" + ) + }); + }); + + $(function () { + $(".expand").blur(function () { + $(this).animate({ + width: '100px' + }, + "slow" + ) + }); + }); + +//------------------------------------------------------------------------------------------------------ +// Write the Attribute Value +//------------------------------------------------------------------------------------------------------ + +function yaahm_setAttribute(name, attr, val) { + //set Yaahm Attribute + var location = document.location.pathname; + if (location.substr(location.length -1, 1) == '/') { + location = location.substr(0, location.length -1); + } + var url = document.location.protocol + "//" + document.location.host + location; + FW_cmd(url + '?XHR=1&cmd.' + name + '=attr ' + name + ' ' + encodeParm(attr) + ' ' + encodeParm(val)); +} + +//------------------------------------------------------------------------------------------------------ +// Change mode and state, set next time +//------------------------------------------------------------------------------------------------------ + +function yaahm_mode(name,targetmode) { + var location = document.location.pathname; + if (location.substr(location.length -1, 1) == '/') { + location = location.substr(0, location.length -1); + } + var url = document.location.protocol + "//" + document.location.host + location; + + FW_cmd(url + '?XHR=1&cmd.' + name + '={main::YAAHM_mode("' + name + '","' + targetmode + '")}'); +} + +function yaahm_state(name,targetstate) { + var location = document.location.pathname; + if (location.substr(location.length -1, 1) == '/') { + location = location.substr(0, location.length -1); + } + var url = document.location.protocol + "//" + document.location.host + location; + + FW_cmd(url + '?XHR=1&cmd.' + name + '={main::YAAHM_state("' + name + '","' + targetstate + '")}'); +} + +function yaahm_setnext(name,i) { + var location = document.location.pathname; + if (location.substr(location.length -1, 1) == '/') { + location = location.substr(0, location.length -1); + } + var url = document.location.protocol + "//" + document.location.host + location; + + var nval; + if (document.getElementById('wt' + i + '_n') !== null) { + nval = document.getElementById('wt' + i + '_n').value; + } else { + nval = "undef"; + } + + FW_cmd(url + '?XHR=1&cmd.' + name + '={main::YAAHM_next("' + name + '","next_' + i + '","' + nval + '")}'); +} + +//------------------------------------------------------------------------------------------------------ +// Start the daily timer +//------------------------------------------------------------------------------------------------------ + +function yaahm_startDayTimer(name) { + + var location = document.location.pathname; + if (location.substr(location.length -1, 1) == '/') { + location = location.substr(0, location.length -1); + } + var url = document.location.protocol + "//" + document.location.host + location; + + // saving start and end times + for (var i = 0; i < dailyno; i++) { + var sval, eval, xval, aval1, aval2; + if ( (dailykeys[i] != 'wakeup') && (dailykeys[i] != 'sleep') ) { + if (document.getElementById('dt' + dailykeys[i] + '_s') !== null) { + sval = document.getElementById('dt' + dailykeys[i] + '_s').value; + } else { + sval = "undef" + } + if (document.getElementById('dt' + dailykeys[i] + '_e') !== null) { + eval = document.getElementById('dt' + dailykeys[i] + '_e').value; + } else { + eval = "undef" + } + if (document.getElementById('dt' + dailykeys[i] + '_x') !== null) { + xval = encodeParm(document.getElementById('dt' + dailykeys[i] + '_x').value); + } else { + xval = "undef" + } + aval1 = $("input[name='actim" + dailykeys[i] + "']:checked").map(function(){ + return $(this).val(); + }).get(); + aval2 = $("input[name='actid" + dailykeys[i] + "']:checked").map(function(){ + return $(this).val(); + }).get(); + FW_cmd(url + '?XHR=1&cmd.' + name + '={main::YAAHM_setParm("' + name + '","dt","' + dailykeys[i] + '",' + '"' + sval + '","' + eval + '","' + xval + '","' + aval1 + ';' + aval2 + '")}'); + } + } + // really start it now + FW_cmd(url+'?XHR=1&cmd.' + name + ' ={main::YAAHM_startDayTimer("' + name + '")}'); + + // change link + $('#dtlink').html(''+name+'.dtimer.IF'); +} + +//------------------------------------------------------------------------------------------------------ +// Weekly profile +//------------------------------------------------------------------------------------------------------ + +function yaahm_startWeeklyTimer(name) { + + var location = document.location.pathname; + if (location.substr(location.length -1, 1) == '/') { + location = location.substr(0, location.length -1); + } + var url = document.location.protocol + "//" + document.location.host + location; + + // saving start weekly times + // iterate over different weekly tables + for (var i = 0; i < weeklyno; i++) { + var xval; + var nval; + var aval1,aval2; + var sval =[ "", "", "", "", "", "", ""]; + //action + if (document.getElementById('wt' + i + '_x') !== null) { + xval = encodeParm(document.getElementById('wt' + i + '_x').value); + } else { + xval = "undef" + } + //next time - attention, field is in toptable + if (document.getElementById('wt' + i + '_n') !== null) { + nval = document.getElementById('wt' + i + '_n').value; + } else { + nval = "undef" + } + //activity + aval1 = $("input[name='acti_" + i + "_m']:checked").map(function(){ + return $(this).val(); + }).get(); + + aval2 = $("input[name='acti_" + i + "_d']:checked").map(function(){ + return $(this).val(); + }).get(); + + //iterate over days of week + for (var j = 0; j < 7; j++) { + if (document.getElementById('wt' + weeklykeys[j] + i +'_s') !== null) { + sval[j] = document.getElementById('wt' + weeklykeys[j] + i + '_s').value; + } else { + sval[j] = "undef"; + } + } + + FW_cmd(url + '?XHR=1&cmd.' + name + '={main::YAAHM_setParm("' + name + '","wt","' + i + '","' + xval + '","' + nval + '","' + aval1 + '","' + aval2 + '","'+ sval.join('","') + '")}'); + } + // really start it now + FW_cmd(url+'?XHR=1&cmd.' + name + ' ={main::YAAHM_startWeeklyTimer("' + name + '")}'); + + // change links + for (var i = 0; i < weeklyno; i++) { + $('#wt'+i+'link').html('' + name + '.wtimer_' + i + '.IF'); + } +} + + +