######################################################################################## # # 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($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.54"; my $firstcall = 1; my %yaahm_transtable_EN = ( "ok" => "OK", "notok" => "Not OK", "start" => "Start", "status" => "Status", "notstarted" => "Not started", "next" => "Next", "manual" => "Manual Time", "exceptly" => "exceptionally", "undecid" => "not decidable", "off" => "off", "swoff" => "switched off", "and" => "and", "clock" => "", "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", "transition" => "Transition to", "onlposfrm" => "only possible from", "notposfrm" => "not possible from", #-- "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", "donotdisturb" => "DoNotDisturb", #-- "state" => "Security", "secstate" => "Device states", "unlocked" => "Unlocked", "locked" => "Locked", "unsecured" => "Not Secured", "secured" => "Secured", "protected" => "Protected", "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" => "Manuelle Zeit", "clock" => "Uhr", "exceptly" => "ausnahmsweise", "undecid" => "nicht bestimmbar", "off" => "Aus", "swoff" => "ausgeschaltet", "and" => "und", "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", "transition" => "Übergang zu", "onlposfrm" => "nur möglich aus", "notposfrm" => "nicht möglich aus", #-- "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", "donotdisturb" => "Nicht Stören", #-- "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","donotdisturb"); my @states = ( "unsecured","secured","protected","guarded"); my @seasons = ( "winter","spring","summer","fall"); #-- modes or day types that affect the profile my @profmode = ("party","absence","donotdisturb"); my @profday = ("vacation","holiday"); #-- color schemes my @csmode; my @csmode1 = ("#53f3c7","#8bfa56","#ff9458","#fd5777"); my @csstate; my @csstate1 = ("#53f3c7","#ff9458","#f554e2","#fd5777"); #-- temporary fix for update purpose sub YAAHM_restore($$){}; sub YAAHM_setWeeklyTime($){}; ######################################################################################### # # 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 ". "modecolor0 modecolor1 modecolor2 modecolor3 statecolor0 statecolor1 statecolor2 statecolor3 ". "timeHelper modeHelper modeAuto:0,1 stateDevices:textField-long stateInterval noicons:0,1 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"}; #-- default colors @csmode = @csmode1; @csstate = @csstate1; $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; } #-- default colors @csmode = @csmode1; @csstate = @csstate1; # 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; my $date = YAAHM_restore($hash,0); #-- data seems to be ok, restore if( defined($date) ){ YAAHM_restore($hash,1); Log3 $name,1,"[YAAHM_Define] data hash restored from save file with date $date"; #-- intialization }else{ Log3 $name,1,"[YAAHM_Define] data hash is initialized"; #-- 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 =~ /modecolor(\d)/) ) { my $ci = $1; if( $ci >= 0 && $ci <= 3 ){ $csmode[$ci] = $attrVal; } #--------------------------------------- }elsif ( ($cmd eq "del") && ($attrName =~ /modecolor(\d)/) ) { my $ci = $1; if( $ci >= 0 && $ci <= 3 ){ $csmode[$ci] = $csmode1[$ci]; } #--------------------------------------- }elsif ( ($cmd eq "set") && ($attrName =~ /statecolor(\d)/) ) { my $ci = $1; if( $ci >= 0 && $ci <= 3 ){ $csstate[$ci] = $attrVal; } #--------------------------------------- }elsif ( ($cmd eq "del") && ($attrName =~ /statecolor(\d)/) ) { my $ci = $1; if( $ci >= 0 && $ci <= 3 ){ $csstate[$ci] = $csstate1[$ci]; } #--------------------------------------- }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",time()+ 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 =~ /^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 must be smaller than ".int( @{$hash->{DATA}{"WT"}}); Log3 $name,1,"[YAAHM_Set] ".$msg; return $msg; } $cmd = "next_".$args[0]; }else{ my $if = undef; for( my $i=0;$i{DATA}{"WT"}});$i++){ $if = $i if ($hash->{DATA}{"WT"}[$i]{"name"} eq $args[0] ); }; #-- check if valid if( !defined($if) ){ $msg = "Error: timer name ".$args[0]." not found"; Log3 $name,1,"[YAAHM_Set] ".$msg; return $msg; } $cmd = "next_".$if; } return YAAHM_nextWeeklyTime($name,$cmd,$args[1],$exec); #----------------------------------------------------------- }elsif ( $cmd =~ /^checkstate.*/ ) { YAAHM_InternalTimer("check",time()+ $args[0], "YAAHM_checkstate", $hash, 0); #----------------------------------------------------------- }elsif ( $cmd =~ /^correctstate.*/ ) { YAAHM_correctstate($hash); #----------------------------------------------------------- }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,1); #----------------------------------------------------------- } elsif ( $cmd =~ /^initialize/ ) { $firstcall = 1; YAAHM_updater($hash); YAAHM_InternalTimer("check",time()+ 5, "YAAHM_checkstate", $hash, 0); #----------------------------------------------------------- } 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=2;$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"); #-- delete readings for( my $j=$if;$j<$imax-1;$j++){ $hash->{READINGS}{"ring_".$j}{VAL} = $hash->{READINGS}{"ring_".($j+1)}{VAL}; $hash->{READINGS}{"ring_".$j."_1"}{VAL} = $hash->{READINGS}{"ring_".($j+1)."_1"}{VAL}; $hash->{READINGS}{"next_".$j}{VAL} = $hash->{READINGS}{"next_".($j+1)}{VAL}; $hash->{READINGS}{"today_".$j}{VAL} = $hash->{READINGS}{"today_".($j+1)}{VAL}; $hash->{READINGS}{"today_".$j."_e"}{VAL} = $hash->{READINGS}{"today_".($j+1)."_e"}{VAL}; $hash->{READINGS}{"tomorrow_".$j}{VAL} = $hash->{READINGS}{"tomorrow_".($j+1)}{VAL}; $hash->{READINGS}{"tomorrow_".$j."_e"}{VAL} = $hash->{READINGS}{"tomorrow_".($j+1)."_e"}{VAL}; $hash->{READINGS}{"tr_wake_".$j}{VAL} = $hash->{READINGS}{"tr_wake".($j+1)}{VAL}; } fhem("deletereading ".$name." ring_".($imax-1)); fhem("deletereading ".$name." ring_".($imax-1)."_1"); fhem("deletereading ".$name." next_".($imax-1)); fhem("deletereading ".$name." today_".($imax-1)); fhem("deletereading ".$name." today_".($imax-1)."_e"); fhem("deletereading ".$name." tomorrow_".($imax-1)); fhem("deletereading ".$name." tomorrow_".($imax-1)."_e"); fhem("deletereading ".$name." tr_wake_".($imax-1)); #-- 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:noArg correctstate:noArg checkstate:0,5,10 restore:noArg initialize:noArg createWeekly deleteWeekly"; } } ######################################################################################### # # YAAHM_Get - Implements the Get function # # Parameter hash = hash of device addressed # ######################################################################################### sub YAAHM_Get($@) { my ($hash, @args) = @_; my $res = ""; my $msg; my $name = $args[0]; my $arg = (defined($args[1]) ? $args[1] : ""); if ($arg eq "version") { return "YAAHM.version => $yaahmversion"; }elsif ($arg eq "test") { YAAHM_testWeeklyTime($hash); return "ok"; }elsif ( $arg eq "next" || $arg eq "sayNext" ){ my $if; #--timer address if( $args[2] =~ /^\d+/ ) { #-- check if valid if( $args[2] >= int(@{$hash->{DATA}{"WT"}}) ){ $msg = "Error, timer number ".$args[2]." does not exist, number musst be smaller than ".int( @{$hash->{DATA}{"WT"}}); Log3 $name,1,"[YAAHM_Get] ".$msg; return $msg; } $if=$args[2]; }else{ $if = undef; for( my $i=0;$i{DATA}{"WT"}});$i++){ $if = $i if ($hash->{DATA}{"WT"}[$i]{"name"} eq $args[1] ); }; #-- check if valid if( !defined($if) ){ $msg = "Error: timer name ".$args[2]." not found"; Log3 $name,1,"[YAAHM_Get] ".$msg; return $msg; } } if( $arg eq "next" ){ return YAAHM_sayWeeklyTime($hash,$if,0); }else{ return YAAHM_sayWeeklyTime($hash,$if,1); } }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 \$dndist = (ReadingsVal(\"".$name."\",\"housemode\",\"\") eq \"donotdisturb\") ? 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". " my \$dndist = (ReadingsVal(\"".$name."\",\"housemode\",\"\") eq \"donotdisturb\") ? 1 : 0;\n"; #-- iterate through table for( my $i=0;$i{DATA}{"WT"}});$i++){ $res .= ",".$i; } return "Unknown argument $arg choose one of next:".$res." sayNext:".$res." version:noArg template:noArg"; } } ######################################################################################### # # YAAHM_save # # Parameter hash = hash of the YAAHM device # ######################################################################################### sub YAAHM_save($) { my ($hash) = @_; my $date = TimeNow(); $hash->{DATA}{"savedate"} = $date; readingsSingleUpdate( $hash, "savedate", $date, 1 ); my $jhash0 = toJSON($hash->{DATA}); my $error = FileWrite("YAAHMFILE",$jhash0); #Log 1,"[YAAHM_save] error=$error"; return; } ######################################################################################### # # YAAHM_restore # # Parameter hash = hash of the YAAHM device # ######################################################################################### sub YAAHM_restore($$) { my ($hash,$doit) = @_; my $name = $hash->{NAME}; my ($error,$jhash0) = FileRead("YAAHMFILE"); if( defined($error) && $error ne "" ){ Log3 $name,1,"[YAAHM_restore] read error=$error"; return undef; } my $json = JSON->new->utf8; my $jhash1 = eval{ $json->decode( $jhash0 ) }; my $date = $jhash1->{"savedate"}; #-- just for the first time, reading an old savefile $date = localtime(time) if( !defined($date)); readingsSingleUpdate( $hash, "savedate", $date, 0 ); if( $doit==1 ){ $hash->{DATA} = {%{$jhash1}}; Log3 $name,5,"[YAAHM_restore] Data hash restored from save file with date ".$date; return 1; }else{ return $date; } } ######################################################################################### # # 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; } return $msg; #-- weekly profile }elsif ($cmd eq "wt") { #-- action $hash->{DATA}{"WT"}[$a[1]]{"action"} = $a[2]; #-- next time $val = $a[3]; if( ($val eq "undef")||($val eq "") ){ $val = undef; }elsif( $val =~/^off/ ){ #-- ok }elsif( $val =~ /\d?\d:\d\d/ ){ #-- ok 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 = "off"; } }else{ $msg = "wrong time specification next=$val for weekly timer ".$a[1].", must be hh:mm of 'off'"; Log 1,"[YAAHM_setParm] ".$msg; $val = "off"; } #-- next waketime $hash->{DATA}{"WT"}[$a[1]]{"next"} = $val; #-- activity party/absence $hash->{DATA}{"WT"}[$a[1]]{"acti_m"} = $a[4]; #-- activity vacation/holiday $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( $val =~/^off/ ){ #-- ok }elsif( $val =~ /\d?\d:\d\d/ ){ #-- 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 = "off"; } }else{ $msg = "wrong time specification $val for weekly timer ".$a[1].", must be hh:mm or 'off'"; Log 1,"[YAAHM_setParm] ".$msg; $val = "off"; } $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 # # Careful: $exec=0 is needed in case we want to prevent execution of helper here # ######################################################################################### 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; } my $ma = defined($attr{$name}{"modeAuto"}) && ($attr{$name}{"modeAuto"} == 1); my $sa = defined($attr{$name}{"stateAuto"}) && ($attr{$name}{"stateAuto"} == 1); #Log 1,"===================> YAAHM Fehlersuche ma,sa = $ma,$sa exec=$exec"; #-- automatically leave party mode at morning time or when going to bed # TODO # TODO Fehler ! Wird nach party um Mitternacht aufgerufen und sichert das Haus, aber in jeder Zeile ein fehler undefined if( $currmode eq "party" && $targettime =~ /(morning)|(sleep)/ && $ma ){ $msg = YAAHM_mode($name,"normal",$exec)."\n"; $msg .= YAAHM_state($name,"secured",$exec)."\n" if( $currstate eq "unsecured" && $targettime eq "sleep" && $sa ); #-- automatically leave absence mode at wakeup time # Ist das wirklich clever ? Was passiert, wenn der Wecker nicht ausgestellt wurde }elsif( $currmode eq "absence" && $targettime =~ /(wakeup)/ && $ma ){ $msg = YAAHM_mode($name,"normal",$exec)."\n"; #-- automatically leave donotdisturb mode at any time event }elsif( $currmode eq "donotdisturb" && $ma ){ $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)/ && $sa ){ $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}); if( $targettime eq "wakeup" ){ $hash->{DATA}{"WT"}[0]{"next"} = ""; readingsBulkUpdate($hash,"next_0",""); }elsif( $targettime eq "sleep" ){ $hash->{DATA}{"WT"}[1]{"next"} = ""; readingsBulkUpdate($hash,"next_1",""); } readingsEndUpdate($hash,1); YAAHM_setWeeklyTime($hash); #-- helper function not executed, e.g. by call from external timer return if( !defined($exec) || $exec==0); #-- execute the helper function my $xval; my $ival; my $wupn; #-- todo here: what should we do, if the timer is NOT enabled and we get up or go to bed anyhow ??? 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 ){ Log3 $name,1,"[YAAHM_time] ecxecuting $xval"; fhem($xval); }elsif( $exec==0 ){ Log3 $name,1,"[YAAHM_time] ".$msg; return $msg; } } ######################################################################################### # # YAAHM_nextWeeklyTime - set the next weekly time # # Parameter name = name of device addressed # ######################################################################################### sub YAAHM_nextWeeklyTime { my ($name,$cmd,$time,$exec) = @_; my $hash = $defs{$name}; my $msg; #--determine which timer (duplicate check when coming from set) $cmd =~ /.*next_([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_nextWeeklyTime] ".$msg; return $msg; } $time = lc($time); #-- check value - may be empty if( $time eq "" || $time eq "default" ){ $time = ""; #-- nontrivial }else{ #-- 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_nextWeeklyTime] ".$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_nextWeeklyTime] ".$msg; return $msg; } } #-- all logic in setweeklytime $hash->{DATA}{"WT"}[$i]{"next"} = $time; YAAHM_setWeeklyTime($hash); } ######################################################################################### # # 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 = ""; my $tr_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"; $tr_msg = $yaahm_tt->{transition}.' "'.$yaahm_tt->{$targetmode}.'" '.$yaahm_tt->{"onlposfrm"}.' '.$yaahm_tt->{"mode"}.'="'.$yaahm_tt->{"normal"}.'"'; #-- 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 from unsecured state"; $tr_msg = $yaahm_tt->{transition}.' "'.$yaahm_tt->{$targetmode}.'" '.$yaahm_tt->{"onlposfrm"}.' '.$yaahm_tt->{"state"}.'="'.$yaahm_tt->{"unsecured"}.'"'; } #-- don't if( $msg ne "" ){ Log3 $name,1,"[YAAHM_mode] ".$msg; readingsSingleUpdate($hash,"tr_errmsg",$tr_msg,1); return $msg; } $hash->{DATA}{"HSM"}{"mode"} = $targetmode; readingsBeginUpdate($hash); readingsBulkUpdate($hash,"tr_errmsg",""); 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 = ""; my $tr_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 if( $targetstate ne "unlocked" && $currmode eq "party" ){ $msg = "Not possible in party mode"; $tr_msg = $yaahm_tt->{transition}.' "'.$yaahm_tt->{$targetstate}.'" '.$yaahm_tt->{"notposfrm"}.' '.$yaahm_tt->{"mode"}.'="'.$yaahm_tt->{"party"}.'"'; } #-- don't if( $msg ne "" ){ Log3 $name,1,"[YAAHM_state] ".$msg; readingsSingleUpdate($hash,"tr_errmsg",$tr_msg,1); return $msg; } $hash->{DATA}{"HSM"}{"state"} = $targetstate; readingsBeginUpdate($hash); readingsBulkUpdate($hash,"tr_errmsg",""); readingsBulkUpdate($hash,"prev_housestate",$prevstate); readingsBulkUpdate($hash,"housestate",$targetstate); readingsBulkUpdate($hash,"tr_housestate",$yaahm_tt->{$targetstate}); readingsEndUpdate($hash,1); #-- 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_InternalTimer("check",time()+ 30, "YAAHM_checkstate", $hash, 0); } ######################################################################################### # # 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".$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_correctstate - correct state devices # # Parameter someHash = either internal hash of timer # => need to dereference it for getting device hash # or device hash # ######################################################################################### sub YAAHM_correctstate($) { my ($someHash) = @_; my $hash; if( defined($someHash->{HASH}) ){ $hash = $someHash->{HASH}; }else{ $hash = $someHash; } my $name = $hash->{NAME}; Log3 $name, 1,"[YAAHM_correctstate] 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{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; #--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 ""; #-- uh oh, double execution of functions !!! Do this in YAAHM_time, NOT in timer #my $xval = "{YAAHM_time('".$name."','".$key."')},".$hash->{DATA}{"DT"}{$key}[2]; my $xval = "{YAAHM_time('".$name."','".$key."',1)}"; #-- entries in the default table with no entry are single-timers if( !$f1 and !$f2 ){ $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 ){ $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 ){ $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 ){ $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 - explicitly in timer, not in YAAHM_time my $xval = ""; if( $i==0 ){ $xval = "{YAAHM_time('".$name."','wakeup',0)},".$hash->{DATA}{"WT"}[$i]{"action"}; }elsif( $i==1 ){ $xval = "{YAAHM_time('".$name."','sleep',0)},".$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}; #-- weekly profile times my ($sg0,$sg1,$sg0mod,$sg1mod,$sg0en,$sg1en,$ring_0,$ring_1,$ng); #-- iterate over timers for( my $i=0;$i{DATA}{"WT"}} );$i++){ #-- obtain next time spec => will override all $ng = $hash->{DATA}{"WT"}[$i]{ "next" }; #-- highest priority is a disabled timer - no wakeup at all if( ReadingsVal($name.".wtimer_".$i.".IF","mode","") eq "disabled" ){ $sg0 = "off"; $sg1 = "off"; $sg0en = "disabled (timer)"; $sg1en = "disabled (timer)"; #-- if the timer is enabled, we'll use its timing values }else{ $sg0 = $hash->{DATA}{"WT"}[$i]{ $weeklytable[$hash->{DATA}{"DD"}[0]{"weekday"}] } ; $sg1 = $hash->{DATA}{"WT"}[$i]{ $weeklytable[$hash->{DATA}{"DD"}[1]{"weekday"}] }; $sg0en = "enabled"; $sg1en = "enabled"; #-- next higher priority for "off" is daytype my $wupad = $hash->{DATA}{"WT"}[$i]{"acti_d"}.",workday,weekend"; #-- start with tomorrow if( index($wupad, $hash->{DATA}{"DD"}[1]{"daytype"}) == -1 ){ $sg1mod = "off (".substr(ReadingsVal($name,"tr_tomorrowType",""),0,3).")"; $sg1en = "disabled (".ReadingsVal($name,"tomorrowType","").")"; }elsif( ($hash->{DATA}{"DD"}[1]{"vacflag"} == 1 ) && index($wupad,"vacation") == -1 ){ $sg1mod = "off (".substr($yaahm_tt->{"vacation"},0,3).")"; $sg1en = "disabled (vacation)"; }else{ $sg1mod = $sg1; } #-- because today we might also have an influence of housemode if( index($wupad, $hash->{DATA}{"DD"}[0]{"daytype"}) == -1 ){ $sg0mod = "off (".substr(ReadingsVal($name,"tr_todayType",""),0,3).")"; $sg0en = "disabled (".ReadingsVal($name,"todayType","").")"; }elsif( ($hash->{DATA}{"DD"}[0]{"vacflag"} == 1 ) && index($wupad,"vacation") == -1 ){ $sg0mod = "off (".substr($yaahm_tt->{"vacation"},0,3).")"; $sg0en = "disabled (vacation)"; }else{ #-- next higher priority for "off" (only today !) is housemode my $wupam = $hash->{DATA}{"WT"}[$i]{"acti_m"}.",normal"; if( index($wupam, ReadingsVal($name,"housemode","")) == -1 ){ $sg0mod = "off (".substr(ReadingsVal($name,"tr_housemode",""),0,3).")"; $sg0en = "disabled (".ReadingsVal($name,"housemode","").")"; }else{ $sg0mod = $sg0; } } } #Log 1,"====> AFTER INITIAL CHECK TIMER $i sg0=$sg0 sg0mod=$sg0mod sg1=$sg1 sg1mod=$sg1mod ng=$ng"; #-- no "next" time specification if( !defined($ng) || $ng eq "" ){ $ring_0 = $sg0; $ring_1 = $sg1; #-- highest priority is a "next" time specification }else{ #-- current time my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time); my $lga = sprintf("%02d%02d",$hour,$min); #-- today's waketime my $tga = $sg0; $tga =~ s/://; #-- tomorrow's waketime my $tgm = $sg1; $tgm =~ s/://; #-- "next" input my $nga = (defined $ng)?$ng:""; $nga =~ s/://; #-- "next" is the same as todays waketime and todays waketime not over => restore ! if( ($nga eq $tga) && ($tga > $lga)){ $ring_0 = $sg0; $ring_1 = $sg1; $ng = ""; $hash->{DATA}{"WT"}[$i]{ "next" }=""; #-- "next" is the same as tomorrows waketime and todays waketime over => restore ! }elsif( ($nga eq $tgm) && ($tga < $lga)){ $ring_0 = $sg0; $ring_1 = $sg1; $ng = ""; $hash->{DATA}{"WT"}[$i]{ "next" }=""; #-- "next" is off }elsif( $nga eq "off" ){ #-- today's waketime not over => we mean today if( $tga ne "off" && ($tga > $lga)){ if( $sg0mod !~ /^off/ ){ $sg0mod = "off (man)"; $ring_0 = "off"; $ring_1 = $sg1; } #-- today's waketime over => we mean tomorrow }else{ if( $sg1mod !~ /^off/ ){ $sg1mod = "off (man)"; $ring_0 = $sg0; $ring_1 = "$sg1 (off)"; } } #-- "next" is nontrivial timespec }else{ #-- "next" after current time => we mean today if( $nga > $lga ){ #-- the same as original waketime => restore ! (do we come here at all ?) #if( $ng eq $sg0 ){ # $sg0mod = $sg0; # $ring_0 = $sg0; # $ng = ""; # $hash->{DATA}{"WT"}[$i]{ "next" } = ""; #-- new manual waketime tomorrow #}else{ $sg0mod = "$ng (man)"; $ring_0 = $ng; #} $ring_1 = $sg1; #-- "next" before current time => we mean tomorrow }else{ #-- the same as original waketime => restore ! (do we come here at all ?) #if( $ng eq $sg1 ){ # $sg0mod = $sg1; # $ring_1 = $sg1; # $ng = ""; # $hash->{DATA}{"WT"}[$i]{ "next" } = ""; #}else{ $sg1mod = "$ng (man)"; $ring_1 = "$sg1 ($ng)"; #} $ring_0 = $sg0; } } } $hash->{DATA}{"WT"}[$i]{"ring_0"} = $ring_0; $hash->{DATA}{"WT"}[$i]{"ring_1"} = $ring_1; $hash->{DATA}{"WT"}[$i]{"ring_0x"} = $sg0mod; $hash->{DATA}{"WT"}[$i]{"ring_1x"} = $sg1mod; $hash->{DATA}{"WT"}[$i]{"ring_0e"} = $sg0en; $hash->{DATA}{"WT"}[$i]{"ring_1e"} = $sg1en; #Log 1,"====> AFTER FINAL CHECK TIMER $i sg0=$sg0 sg0mod=$sg0mod sg1=$sg1 sg1mod=$sg1mod ng=$ng"; #Log 1," ".$hash->{DATA}{"WT"}[$i]{"ring_0x"}." ".$hash->{DATA}{"WT"}[$i]{"ring_1x"}; #-- notation: # today_i is today's waketime of timer i # tomorrow_i is tomorrow's waketime of timer i # timers have additional conditions for activation according # to housemode and daytype, these conditions are checked in the timer device # devices and are not part of the table. But we have a reading: # today_i_e is a copy of the condition checked in the timer device # (housemode and daytype) # tomorrow_i_e is not a complete copy of the condition checked in the timer device, # (daytype only, because housemode of tomorrow is not known) # ring_[i]_1 is tomorrow's ring time of timer i readingsBeginUpdate($hash); readingsBulkUpdate( $hash, "today_".$i,$sg0 ); readingsBulkUpdate( $hash, "tomorrow_".$i,$sg1 ); readingsBulkUpdate( $hash, "today_".$i."_e",$sg0en ); readingsBulkUpdate( $hash, "tomorrow_".$i."_e",$sg1en ); readingsBulkUpdate( $hash, "ring_".$i,$ring_0 ); readingsBulkUpdate( $hash, "ring_".$i."_1",$ring_1 ); readingsBulkUpdate( $hash, "ring_".$i."x",$sg0mod ); readingsBulkUpdate( $hash, "ring_".$i."_1x",$sg1mod ); readingsBulkUpdate( $hash, "next_".$i,$ng ); readingsEndUpdate($hash,1); YAAHM_sayWeeklyTime($hash,$i,0); } } ######################################################################################### # # YAAHM_sayWeeklyTime - say the next weekly time # # Parameter name = name of device addressed # ######################################################################################### sub YAAHM_sayWeeklyTime($$$) { my ($hash,$timer,$sp) = @_; my $name = $hash->{NAME}; my ($tod,$tom,$ton,$hl,$ml,$tl,$ht,$mt,$tt,$tsay,$chg,$msg,$hw,$mw,$pt,$rea); #--determine which timer (duplicate check when coming from set) if( $timer >= int( @{$hash->{DATA}{"WT"}}) ){ $msg = "Error, timer number $timer does not exist, number musst be smaller than ".int( @{$hash->{DATA}{"WT"}}); Log3 $name,1,"[YAAHM_sayNextTime] ".$msg; return $msg; } #-- init message $msg = $hash->{DATA}{"WT"}[$timer]{"name"}; #-- get timer values from readings, because these include vacation settings and special time $tod = $hash->{DATA}{"WT"}[$timer]{"ring_0x"}; $tom = $hash->{DATA}{"WT"}[$timer]{"ring_1x"}; #-- current local time ($hl,$ml) = split(':',strftime('%H:%M', localtime(time))); $tl = 60*$hl+$ml; #-- today off AND tomorrow any time or off => compare this time with current time if( $tod =~ /^off.*/ ){ #-- tomorrow any time if( $tom =~ /(\d?\d):(\d\d)(:(\d\d))?/ && $tom !~ /.*\(off\)$/ ){ #Log 1,"===========> |$1|$2|$3|$4"; ($ht,$mt) = split('[\s:]',$tom); $tt=60*$ht+$mt; #-- wakeup tomorrow later than now if( $tt < $tl ){ $hw = $1*1; $mw = $2*1; $pt = sprintf("%d:%02d",$hw,$mw)." ".lc($yaahm_tt->{"tomorrow"}); $msg .= " ".lc($yaahm_tt->{"tomorrow"})." $hw ".$yaahm_tt->{"clock"}; $msg .=" $mw" if( $mw != 0 ); }else{ $pt = "off ".lc($yaahm_tt->{"today"}); $msg .= " ".lc($yaahm_tt->{"today"})." ".$yaahm_tt->{"swoff"}; } }elsif( $tom =~ /^off/ || $tom =~ /.*\(off\)$/ ){ $pt = "off ".lc($yaahm_tt->{"today"})." ".$yaahm_tt->{"and"}." ".lc($yaahm_tt->{"tomorrow"}); $msg .= " ".lc($yaahm_tt->{"today"})." ".$yaahm_tt->{"and"}." ".lc($yaahm_tt->{"tomorrow"})." ".$yaahm_tt->{"swoff"}; }else{ $pt = $yaahm_tt->{"undecid"}; $msg .= " ".$yaahm_tt->{"undecid"}; } #-- today nontrivial => compare this time with current time }elsif( $tod =~ /(\d?\d):(\d\d)(:(\d\d))?/ ){ #Log 1,"===========> |$1|$2|$3|$4"; ($ht,$mt) = split('[\s:]',$tod); $tt=60*$ht+$mt; #-- wakeup later today if( $tt >= $tl ){ $hw = $1*1; $mw = $2*1; $pt = sprintf("%d:%02d",$hw,$mw)." ".lc($yaahm_tt->{"today"}); $msg .= " ".lc($yaahm_tt->{"today"})." $hw ".$yaahm_tt->{"clock"}; $msg .=" $mw" if( $mw != 0 ); #-- todays time already past => tomorrow - but this may be off }elsif( ($tom eq "off") || ($tom =~ /.*\(off\)/) ){ $pt = "off ".lc($yaahm_tt->{"tomorrow"}); $msg .= " ".lc($yaahm_tt->{"tomorrow"})." ".$yaahm_tt->{"swoff"}; }elsif( $tom =~ /(\d?\d):(\d\d)(:(\d\d))?( \((\d?\d):(\d\d)(:(\d\d))?\))?/ ){ #Log 1,"===========> |$1|$2|$3|$4|$5|$6"; if( defined($5) && $5 ne ""){ $hw = $6*1; $mw = $7*1; }else{ $hw = $1*1; $mw = $2*1; } $pt = sprintf("%d:%02d",$hw,$mw)." ".lc($yaahm_tt->{"tomorrow"}); $msg .= " ".lc($yaahm_tt->{"tomorrow"})." $hw ".$yaahm_tt->{"clock"}; $msg .=" $mw" if( $mw != 0 ); }elsif( $tom =~ /^off/ || $tom =~ /.*\(off\)$/ ){ $pt = "off ".lc($yaahm_tt->{"tomorrow"}); $msg .= " ".lc($yaahm_tt->{"tomorrow"})." ".$yaahm_tt->{"swoff"}; }else{ $pt = $yaahm_tt->{"undecid"}; $msg .= " ".$yaahm_tt->{"undecid"}; } }else{ $pt = $yaahm_tt->{"undecid"}; $msg .= " ".$yaahm_tt->{"undecid"}; } $hash->{DATA}{"WT"}[$timer]{"wake"} = $pt; readingsSingleUpdate($hash,"tr_wake_".$timer,$pt,1); if( $sp==0 ){ return $pt }else{ return $msg; } } ######################################################################################### # # 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,$tod); my $todaylong = ""; my $tomlong = ""; my $twodaylong= ""; #-- Vorschau täglich oder wenn neu gestartet if( ($event eq "test") || (($event eq 'event') && ($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('%m-%d', localtime(time)); $stom = strftime('%m-%d', localtime(time+86400)); $stwom = strftime('%m-%d', localtime(time+2*86400)); $tod = holiday_refresh( $specialDev, $stoday ); if ( $tod ne "none" ) { $todaydesc .= $tod.","; Log3 $name, 5,"[YAAHM] found today=special date \"$tod\" in holiday $specialDev"; } $tod = holiday_refresh( $specialDev, $stom ); if ( $tod ne "none" ) { $tomdesc .= $tod.","; Log3 $name, 5,"[YAAHM] found tomorrow=special date \"$tod\" in holiday $specialDev"; } $tod = holiday_refresh( $specialDev, $stwom ); if ( $tod ne "none" ) { $twomdesc .= $tod.","; Log3 $name, 5,"[YAAHM] found twodays=special date \"$tod\" in holiday $specialDev"; } #-- device of type calendar }elsif( IsDevice($specialDev, "Calendar" )){ $stoday = strftime('%d.%m.%Y', localtime(time)); $stom = strftime('%d.%m.%Y', localtime(time+86400)); $stwom = strftime('%d.%m.%Y', localtime(time+2*86400)); @tday = split('\.',$stoday); @tmor = split('\.',$stom); @two = split('\.',$stwom); $fline=Calendar_Get($defs{$specialDev},"get","full","mode=alarm|start|upcoming"); #-- more complicated to check here, # format is ' upcoming [ ] - [] my ($cstart,$cdesc); if($fline){ #chomp($fline); @lines = split('\n',$fline); foreach $fline (@lines){ chomp($fline); @chunks = split(' ',$fline); if( int(@chunks)>=7 ){ $cstart = 4; $cdesc = 7; }else{ $cstart = 2; $cdesc = 5, } @sday = split('\.',$chunks[$cstart]); $tod = ($chunks[$cdesc]) ? $chunks[$cdesc] : "???"; #-- today my $rets = ($sday[2]-$tday[2])*365+($sday[1]-$tday[1])*31+($sday[0]-$tday[0]); if( $rets==0 ){ $todaydesc .= $tod.","; Log3 $name, 5,"[YAAHM] found today=special date \"$tod\" in calendar $specialDev"; } $rets = ($sday[2]-$tmor[2])*365+($sday[1]-$tmor[1])*31+($sday[0]-$tmor[0]); if( $rets==0 ){ $tomdesc .= $tod.","; Log3 $name, 5,"[YAAHM] found tomorrow=special date \"$tod\" in calendar $specialDev"; } $rets = ($sday[2]-$two[2])*365+($sday[1]-$two[1])*31+($sday[0]-$two[0]); if( $rets==0 ){ $twomdesc .= $tod.","; Log3 $name, 5,"[YAAHM] found twodays=special date \"$tod\" in calendar $specialDev"; } } } }else{ Log3 $name, 1,"[YAAHM] unknown special device $specialDev"; } #-- accumulate descriptions $todaylong .= $todaydesc if($todaydesc); $tomlong .= $tomdesc if($tomdesc); $twodaylong .= $twomdesc if($twomdesc); } $todaylong =~ s/,$//; $tomlong =~ s/,$//; $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); readingsBulkUpdateIfChanged( $hash, "twodaysSpecial",$twodaylong); 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,@ttwo); my ($todaydesc,$todaytype,$tomdesc,$tomtype,$twodesc,$twotype); my $stoday = strftime('%d.%m.%Y', localtime(time)); my $stom = strftime('%d.%m.%Y', localtime(time+86400)); my $stwo = strftime('%d.%m.%Y', localtime(time+2*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"}; $hash->{DATA}{"DD"}[0]{"vacflag"} = 0; $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"}; $hash->{DATA}{"DD"}[1]{"vacflag"} = 0; $twotype = "workday"; $hash->{DATA}{"DD"}[2]{"date"} = $stwo; $hash->{DATA}{"DD"}[2]{"weekday"} = (strftime('%w', localtime(time+2*86400))+6)%7; $hash->{DATA}{"DD"}[2]{"daytype"} = "workday"; $hash->{DATA}{"DD"}[2]{"desc"} = $yaahm_tt->{"workday"}; $hash->{DATA}{"DD"}[2]{"vacflag"} = 0; #-- 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('%m-%d', localtime(time)); $stom = strftime('%m-%d', localtime(time+86400)); $stwo = strftime('%m-%d', localtime(time+2*86400)); my $tod = holiday_refresh( $vacdayDev, $stoday ); if ( $tod ne "none" ) { $todaydesc = $tod; $todaytype = "vacday"; Log3 $name, 5,"[YAAHM] found today=vacation \"$todaydesc\" in holiday $vacdayDev"; } $tod = holiday_refresh( $vacdayDev, $stom ); if ( $tod ne "none" ) { $tomdesc = $tod; $tomtype = "vacday"; Log3 $name, 5,"[YAAHM] found tomorrow=vacation \"$tomdesc\" in holiday $vacdayDev"; } $tod = holiday_refresh( $vacdayDev, $stwo ); if ( $tod ne "none" ) { $twodesc = $tod; $twotype = "vacday"; Log3 $name, 5,"[YAAHM] found twodays=vacation \"$twodesc\" in holiday $vacdayDev"; } #-- device of type calendar }elsif( IsDevice($vacdayDev, "Calendar" )){ $stoday = strftime('%d.%m.%y', localtime(time)); $stom = strftime('%d.%m.%y', localtime(time+86400)); $stwo = strftime('%d.%m.%y', localtime(time+2*86400)); @tday = split('\.',$stoday); @tmor = split('\.',$stom); @ttwo = split('\.',$stwo); #-- 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, 5,"[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, 5,"[YAAHM] found tomorrow=vacation \"$tomdesc\" in calendar $vacdayDev"; } $rets = ($sday[2]-$ttwo[2])*365+($sday[1]-$ttwo[1])*31+($sday[0]-$ttwo[0]); $rete = ($eday[2]-$ttwo[2])*365+($eday[1]-$ttwo[1])*31+($eday[0]-$ttwo[0]); if( ($rete>=0) && ($rets<=0) ){ $twodesc = $chunks[5]; $twotype = "vacation"; Log3 $name, 5,"[YAAHM] found twodays=vacation \"$twodesc\" 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; $hash->{DATA}{"DD"}[0]{"vacflag"} = 1; } if( $tomtype eq "vacation" ){ $hash->{DATA}{"DD"}[1]{"daytype"} = "vacation"; $hash->{DATA}{"DD"}[1]{"desc"} = $tomdesc; $hash->{DATA}{"DD"}[1]{"vacflag"} = 1; } if( $twotype eq "vacation" ){ $hash->{DATA}{"DD"}[2]{"daytype"} = "vacation"; $hash->{DATA}{"DD"}[2]{"desc"} = $twodesc; $hash->{DATA}{"DD"}[2]{"vacflag"} = 1; } #-- 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"; } if( strftime('%u', localtime(time+2*86400)) > 5){ $twotype = "weekend"; if( $hash->{DATA}{"DD"}[2]{"daytype"} ne "workday" ){ $hash->{DATA}{"DD"}[2]{"desc"} = $yaahm_tt->{"weekend"}.", ".$hash->{DATA}{"DD"}[2]{"desc"}; }else{ $hash->{DATA}{"DD"}[2]{"desc"} = $yaahm_tt->{"weekend"}; } $hash->{DATA}{"DD"}[2]{"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('%m-%d', localtime(time)); $stom = strftime('%m-%d', localtime(time+86400)); $stwo = strftime('%m-%d', localtime(time+2*86400)); my $tod = holiday_refresh( $holidayDev, $stoday ); if ( $tod ne "none" ) { $todaydesc = $tod; $todaytype = "holiday"; Log3 $name, 5,"[YAAHM] found today=holiday \"$todaydesc\" in holiday $holidayDev"; } $tod = holiday_refresh( $holidayDev, $stom ); if ( $tod ne "none" ) { $tomdesc = $tod; $tomtype = "holiday"; Log3 $name, 5,"[YAAHM] found tomorrow=holiday \"$tomdesc\" in holiday $holidayDev"; } $tod = holiday_refresh( $holidayDev, $stwo ); if ( $tod ne "none" ) { $twodesc = $tod; $twotype = "holiday"; Log3 $name, 5,"[YAAHM] found twodays=holiday \"$twodesc\" in holiday $holidayDev"; } #-- device of type calendar }elsif( IsDevice($holidayDev, "Calendar" )){ $stoday = strftime('%d.%m.%y', localtime(time)); $stom = strftime('%d.%m.%y', localtime(time+86400)); $stwo = strftime('%d.%m.%y', localtime(time+2*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, 5,"[YAAHM] found today=holiday \"$todaydesc\" in calendar $holidayDev"; } if( $date eq $stom ){ $tomdesc = substr($line,15); $tomtype = "holiday"; Log3 $name, 5,"[YAAHM] found tomorrow=holiday \"$tomdesc\" in calendar $holidayDev"; } if( $date eq $stwo ){ $twodesc = substr($line,15); $twotype = "holiday"; Log3 $name, 5,"[YAAHM] found twodays=holiday \"$twodesc\" 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"; } if( $twotype eq "holiday" ){ if( $hash->{DATA}{"DD"}[2]{"daytype"} ne "workday" ){ $hash->{DATA}{"DD"}[2]{"desc"} = $twodesc.", ".$hash->{DATA}{"DD"}[2]{"desc"}; }else{ $hash->{DATA}{"DD"}[2]{"desc"} = $twodesc; } $hash->{DATA}{"DD"}[2]{"daytype"} = "holiday"; } #-- sunrise, sunset and the offsets YAAHM_sun($hash); YAAHM_sunoffsets($hash); readingsBeginUpdate($hash); #-- and do not forget to put them into readings, because these are read by the timer foreach my $key (sort YAAHM_dsort keys %defaultdailytable){ my $f1 = defined($defaultdailytable{$key}[0]); my $f2 = defined($defaultdailytable{$key}[1]); #-- entries in the default table with no entry are single-timers if( !$f1 and !$f2 ){ readingsBulkUpdate( $hash, "s_".$key, $hash->{DATA}{"DT"}{$key}[0] ); #-- 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] ); #-- 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] ); #-- 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] ); #-- something wrong }else{ my $msg = "Readings update failed, something wrong with entry ".$key; Log 1,"[YAAHM_GetDayStatus] ".$msg; return $msg; } } 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"} ) } readingsBulkUpdateIfChanged( $hash, "twodaysType",$tomtype ); readingsBulkUpdateIfChanged( $hash, "tr_twodaysType",$yaahm_tt->{$hash->{DATA}{"DD"}[2]{"daytype"}} ); if( $tomtype eq "workday"){ readingsBulkUpdateIfChanged( $hash, "twodaysDesc","--" ) }elsif( $tomtype eq "vacation"){ readingsBulkUpdateIfChanged( $hash, "twodaysDesc",$hash->{DATA}{"DD"}[2]{"desc"} ) }elsif( $tomtype eq "weekend"){ readingsBulkUpdateIfChanged( $hash, "twodaysDesc","--" ) }else{ readingsBulkUpdateIfChanged( $hash, "twodaysDesc",$hash->{DATA}{"DD"}[2]{"desc"} ) } YAAHM_setWeeklyTime($hash); readingsEndUpdate($hash,1); return undef; } ######################################################################################### # # YAAHM_sun - obtain time offsets for midnight etc. sunrise and sunset # # Parameter hash = hash of device addressed # ######################################################################################### sub YAAHM_sun($) { my ($hash) = @_; my $name = $hash->{NAME}; #-- sunrise and sunset today and tomorrow and in two days my ($sttoday,$sttom,$sttwo); my $count = 0; my $strise0 = ""; my $strise1 = ""; my $strise2 = ""; my ($msg,$stset0,$stseas0,$stset1,$stseas1,$stset2,$stseas2); $sttoday = strftime('%Y-%m-%d', localtime(time)); #-- since the Astro module sometimes gives us strange results, we need to do this more than once while( $strise0 !~ /^\d\d:\d\d:\d\d/ && $count < 5){ $strise0 = Astro_Get($hash,"dummy","text", "SunRise",$sttoday).":00"; $count++; select(undef,undef,undef,0.01); } if( $count == 5 ){ $msg = "Error, no proper sunrise today return from Astro module in 5 attempts"; Log3 $name,1,"[YAAHM_sun] ".$msg; $strise0 = "06:00:00"; } my ($hour,$min) = split(":",$strise0); $hash->{DATA}{"DD"}[0]{"sunrise"} = sprintf("%02d:%02d",$hour,$min); $stset0 = Astro_Get($hash,"dummy","text", "SunSet",$sttoday).":00"; ($hour,$min) = split(":",$stset0); $hash->{DATA}{"DD"}[0]{"sunset"} = sprintf("%02d:%02d",$hour,$min); $stseas0 = Astro_Get($hash,"dummy","text", "ObsSeasonN",$sttoday); $hash->{DATA}{"DD"}[0]{"season"} = $seasons[$stseas0]; $sttom = strftime('%Y-%m-%d', localtime(time+86400)); $count = 0; $msg = ""; #-- since the Astro module sometimes gives us strange results, we need to do this more than once while( $strise1 !~ /^\d\d:\d\d:\d\d/ && $count < 5){ $strise1 = Astro_Get($hash,"dummy","text", "SunRise",$sttom).":00"; $count++; select(undef,undef,undef,0.01); } if( $count == 5 ){ $msg = "Error, no proper sunrise tomorrow return from Astro module in 5 attempts"; Log3 $name,1,"[YAAHM_sun] ".$msg; $strise1 = "06:00:00"; } ($hour,$min) = split(":",$strise1); $hash->{DATA}{"DD"}[1]{"sunrise"} = sprintf("%02d:%02d",$hour,$min); $stset1 = Astro_Get($hash,"dummy","text", "SunSet",$sttom).":00"; ($hour,$min) = split(":",$stset1); $hash->{DATA}{"DD"}[1]{"sunset"} = sprintf("%02d:%02d",$hour,$min); $stseas1 = Astro_Get($hash,"dummy","text", "ObsSeasonN",$sttom); $hash->{DATA}{"DD"}[1]{"season"} = $seasons[$stseas1]; $sttwo = strftime('%Y-%m-%d', localtime(time+2*86400)); $count = 0; $msg = ""; #-- since the Astro module sometimes gives us strange results, we need to do this more than once while( $strise2 !~ /^\d\d:\d\d:\d\d/ && $count < 5){ $strise2 = Astro_Get($hash,"dummy","text", "SunRise",$sttwo).":00"; $count++; select(undef,undef,undef,0.01); } if( $count == 5 ){ $msg = "Error, no proper sunrise twodays return from Astro module in 5 attempts"; Log3 $name,1,"[YAAHM_sun] ".$msg; $strise2 = "06:00:00"; } ($hour,$min) = split(":",$strise2); $hash->{DATA}{"DD"}[2]{"sunrise"} = sprintf("%02d:%02d",$hour,$min); $stset2 = Astro_Get($hash,"dummy","text", "SunSet",$sttom).":00"; ($hour,$min) = split(":",$stset2); $hash->{DATA}{"DD"}[2]{"sunset"} = sprintf("%02d:%02d",$hour,$min); $stseas2 = Astro_Get($hash,"dummy","text", "ObsSeasonN",$sttwo); $hash->{DATA}{"DD"}[2]{"season"} = $seasons[$stseas2]; } ######################################################################################### # # YAAHM_sunoffsets - obtain time offsets for midnight etc. sunrise and sunset # # Parameter hash = hash of device addressed # ######################################################################################### sub YAAHM_sunoffsets($) { 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_statewidget - returns SVG code for inclusion into page # # Parameter hash = hash of device addressed # ######################################################################################### sub YAAHM_statewidget($){ my ($hash) = @_; my $name = $hash->{NAME}; return "" if( AttrVal($name,"noicons",0) == 1); my $state = $hash->{DATA}{"HSM"}{"state"}; my ($color,$locks,$unlocks,$bells,$eyes); if( $state eq "unsecured" ){ $color=$csstate[0]; $locks="hidden"; $unlocks="visible"; $bells="hidden"; $eyes="hidden"; }elsif( $state eq "secured" ){ $color=$csstate[1]; $locks="visible"; $unlocks="hidden"; $bells="hidden"; $eyes="hidden"; }elsif( $state eq "protected" ){ $color=$csstate[2]; $locks="visible"; $unlocks="hidden"; $bells="visible"; $eyes="hidden"; }elsif( $state eq "guarded" ){ $color=$csstate[3]; $locks="visible"; $unlocks="hidden"; $bells="visible"; $eyes="visible"; }else{ Log 1,"[YAAHM_statewidget] Error, housestate $state not defined"; return; } my $ret = ''; $ret .= '\n'; #-- shield $ret .='\n'. ''. ''; #-- small bell $ret .= '\n'; $ret .= ''. '.\n'; #-- lock $ret .= '\n'; $ret .= '\n'. '\n'; $ret .= '\n'; $ret .= '\n'; $ret .= '\n'; $ret .= '\n'; #-- eye $ret .= '\n'; $ret .= '\n'; $ret .= ''; return $ret } ######################################################################################### # # YAAHM_modewidget - returns SVG code for inclusion into page # # Parameter hash = hash of device addressed # ######################################################################################### sub YAAHM_modewidget($){ my ($hash) = @_; my $name = $hash->{NAME}; return "" if( AttrVal($name,"noicons",0) == 1); my $mode = $hash->{DATA}{"HSM"}{"mode"}; my ($color,$normals,$absents,$partys,$dnds); if( $mode eq "normal" ){ $color=$csmode[0]; $normals="visible"; $partys="hidden"; $absents="hidden"; $dnds="hidden"; }elsif( $mode eq "party" ){ $color=$csmode[1]; $normals="hidden"; $partys="visible"; $absents="hidden"; $dnds="hidden"; }elsif( $mode eq "absence" ){ $color=$csmode[2]; $normals="hidden"; $partys="hidden"; $absents="visible"; $dnds="hidden"; }elsif( $mode eq "donotdisturb" ){ $color=$csmode[3]; $normals="hidden"; $partys="hidden"; $absents="hidden"; $dnds="visible"; }else{ Log 1,"[YAAHM_modewidget] Error, housemode $mode not defined"; return; } my $ret = ''; $ret .= ' '; $ret .= ' '. ' '; $ret .= ' '. ''. ''. ''; $ret .= ''. ''. ''. ''. ''. ''. ''. ''. ''; $ret .= ''. ''. ''. ''. ''; $ret .= ''. ''. ''. ''; $ret .= ''; return $ret } ######################################################################################### # # 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 $sz = ($FW_webArgs{size} ? $FW_webArgs{size} : '400x400'); $sz =~ s/'//g; my @size=split('x',$sz); $FW_RETTYPE = "image/svg+xml"; $FW_RET=""; FW_pO ''; my $hash = $defs{$name}; 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*100)/100; my $y_now = int(cos($a_now)*$radius*100)/100; 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 $sr = $hour + $min*60; my $a_sunrise = (60*$hour + $min)/1440 * 2 * pi; my $x_sunrise = -int(sin($a_sunrise)*$radius*100)/100; my $y_sunrise = int(cos($a_sunrise)*$radius*100)/100; 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 $mo = $hour + $min*60; my $a_morning = (60*$hour + $min)/1440 * 2 * pi; my $x_morning = -int(sin($a_morning)*$radius*100)/100; my $y_morning = int(cos($a_morning)*$radius*100)/100; 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*100)/100; my $y_noon = int(cos($a_noon)*$radius*100)/100; 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*100)/100; my $y_afternoon = int(cos($a_afternoon)*$radius*100)/100; 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 $ss = $hour + $min*60; my $a_sunset = (60*$hour + $min)/1440 * 2 * pi; my $x_sunset = -int(sin($a_sunset)*$radius*100)/100; my $y_sunset = int(cos($a_sunset)*$radius*100)/100; 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 $ev = $hour + $min*60; my $a_evening = (60*$hour + $min)/1440 * 2 * pi; my $x_evening = -int(sin($a_evening)*$radius*100)/100; my $y_evening = int(cos($a_evening)*$radius*100)/100; 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*100)/100; my $y_night = int(cos($a_night)*$radius*100)/100; my $t_midnight = "0:00"; $t_midnight =~ s/^0//; ($hour,$min) = split(":",$t_midnight); my $a_midnight = 0.0; my $x_midnight = 0.0; my $y_midnight = $radius; 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. We need a workaround here for the broken SVG engine of Firefox, splitting this in two arcs FW_pO ''; FW_pO ''; #-- sunrise to morning sector my $dir = ( $sr < $mo ) ? 0 : 1; FW_pO ''; #-- morning to evening sector FW_pO ''; #-- evening to sunset sector $dir = ( $ss > $ev ) ? 1 : 0; 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 $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"}; my $ts; my ($styl,$stym,$styr); my $ret = ""; YAAHM_GetDayStatus($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 .= "
".ReadingsVal($name,"housestate",undef)."
". "
".ReadingsVal($name,"housemode",undef)."
"; $ret .= "\n"; $ret .= "\n"; ### action ################################################################################################ #-- determine columns my $cols = max(max(int(@modes),int(@states)),$weeklyno); $ret .= ""; $ret .= "\n"; ### daily overview ################################################################################################ $styl="border-left:1px solid gray;border-top:1px solid gray;border-top-left-radius:10px;border-top-right-radius:0px;border-bottom-left-radius:0px;"; $stym="border-top:1px solid gray;border-radius:0px;"; $styr="border-right:1px solid gray;border-top:1px solid gray;border-top-right-radius:10px;border-top-left-radius:0px;border-bottom-right-radius:0px;"; $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"}. "
".ReadingsVal($name,"tr_errmsg",undef)."
". "". "". ""; 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 .= ""; } } #style=\"height:20px;border-bottom: 10px solid #333333;background-image: linear-gradient(#e5e5e5,#ababab);\" $ret .= ""; $ret .= "
".YAAHM_modewidget($hash)."
".$yaahm_tt->{"mode"}."
".ReadingsVal($name,"tr_housemode",undef)."
{$modes[$i]}. "\" style=\"width:120px;\" onclick=\"javascript:yaahm_mode('$name','".$modes[$i]."')\"/>
".YAAHM_statewidget($hash)."".$yaahm_tt->{"state"}."
".ReadingsVal($name,"tr_housestate",undef). "
".ReadingsVal($name,"sym_housestate",undef)."
{$states[$i]}. "\" style=\"width:120px;\" onclick=\"javascript:yaahm_state('$name','".$states[$i]."')\"/>

"; #-- repeat manual next for every weekly table my $nval = ""; my $wupn; $ret .= "\n"; for (my $i=0;$i<$weeklyno;$i++){ if($i<$weeklyno-1){ $styl= "border-bottom:1px solid gray;border-top:1px solid gray"; }else{ $styl= "border-bottom:1px solid gray;border-top:1px solid gray;border-right:1px solid gray;border-bottom-right-radius:10px;border-top-right-radius:10px"; } $wupn = $hash->{DATA}{"WT"}[$i]{"name"}; $nval = ( defined($hash->{DATA}{"WT"}[$i]{"next"}) ) ? $hash->{DATA}{"WT"}[$i]{"next"} : ""; $ret .= sprintf("\n",$i,$i,$i); } $ret .= "\n"; $ret .= "
".$yaahm_tt->{"manual"}."$wupn
". "
$nval

".$yaahm_tt->{"overview"}."
"; #-- time widget $ret .= ""; #-- continue summary with headers $ret .= ""; #-- device states $ret .= "\n"; $styl="border-left:1px solid gray;border-radius:0px;"; $stym="border:none"; $styr="border-right:1px solid gray;border-radius:0px;"; $ret .= ""; #-- continue summary with entries $ret .= "\n"; $ret .= "\n"; $ret .= "\n"; $ret .= "\n"; $ret .= "\n"; $styl="border-left:1px solid gray;border-bottom:1px solid gray;border-bottom-left-radius:10px;border-bottom-right-radius:0px;border-top-left-radius:0px;"; $stym="border-bottom:1px solid gray;border-radius:0px;"; $styr="border-right:1px solid gray;border-bottom:1px solid gray;border-bottom-right-radius:10px;border-top-right-radius:0px;border-bottom-left-radius:0px;"; $ret .= "\n"; #-- housetime/phase $ret .= ""; #-- weekly timers 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 = ""; } #-- ring times my $ring_0x = $hash->{DATA}{"WT"}[$i]{"ring_0x"}; my $ring_1x = $hash->{DATA}{"WT"}[$i]{"ring_1x"}; my $wake = $hash->{DATA}{"WT"}[$i]{"wake"}; #-- border styles if( $i==0 ){ $styl="border-left:1px solid gray;border-top:1px solid gray;border-top-left-radius:10px;border-top-right-radius:0px;border-bottom-left-radius:0px;"; $stym="border-top:1px solid gray;border-radius:0px;"; $styr="border-right:1px solid gray;border-top:1px solid gray;border-top-right-radius:10px;border-top-left-radius:0px;border-bottom-right-radius:0px;"; }elsif( $i == $weeklyno-1 ){ $styl="border-left:1px solid gray;border-bottom:1px solid gray;border-bottom-left-radius:10px;border-bottom-right-radius:0px;border-top-left-radius:0px;"; $stym="border-bottom:1px solid gray;border-radius:0px;"; $styr="border-right:1px solid gray;border-bottom:1px solid gray;border-bottom-right-radius:10px;border-top-right-radius:0px;border-bottom-left-radius:0px;"; }else{ $styl="border-left:1px solid gray;border-radius:0px;"; $stym="border:none"; $styr="border-right:1px solid gray;border-radius:0px;"; } $ret.="". "". "". "\n"; } $ret .= "
".$yaahm_tt->{"today"}."".$yaahm_tt->{"tomorrow"}."". "
".ReadingsVal($name,"tr_housestate",undef)."
→". $yaahm_tt->{"secstate"}."
".ReadingsVal($name,"sdev_housestate","")."
".$yaahm_tt->{$weeklytable[$hash->{DATA}{"DD"}[0]{"weekday"}]}[0] . "".$yaahm_tt->{$weeklytable[$hash->{DATA}{"DD"}[1]{"weekday"}]}[0]."
".$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
".$ring_0x."
".$ring_1x."
".$wake."
"; 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 admimistration of profiles for daily, weekly and monthly processes =item summary_DE Verwaltung von Profilen für tägliche, wöchentliche und monatliche Abläufe =begin html

YAAHM

    Yet Another Auto Home Module to set up a cyclic processing of commands (daily, weekly, monthly, yearly profile)

    Usage

    See German Wiki page

    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.
    • This module needs the JSON package

    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", or "default".
      • 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.
      • If the time specification is "off", the next pending waketime will be ignored.
      • If the time specification id "default", the manual waketime is removed and the value from the weekly schedule will be used.
    • set <name> mode normal | party | absence | donotdisturb
      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
      • donotodisturb - 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> checkstate 0|5|10
      Check the house (security) state for each of the devices defined in the stateDevices attribute in 0, 5 or 10 seconds from now
    • set <name> correctstate
      Try to correct the house (security) state, by issueing a FHEM command set <device> <targetstate> for each of the devices defined in the stateDevices attribute and not in the proper state for the given house (security) state
    • set <name> createWeekly <string>
      Create a new weekly profile <string>
    • set <name> deleteWeekly <string>
      Delete the weekly profile <string>
    • set <name> initialize
      Restart the internal timers
    • 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>)
    • set <name> save|restore
      Manually save/restore the complete profile data to/from the external file YAAHMFILE (save done automatically at each timer start, restore at FHEM start)

    Get

    • get <name> next <timernumber>
      get <name> next <timername>
      For the weekly timer identified by its number (starting at 0) or its name, get the next ring time in a format suitable for text devices.
    • get <name> sayNext <timernumber>
      get <name> sayNext <timername>
      For the weekly timer identified by its number (starting at 0) or its name, get the next ring time in a format suitable for speech devices.
    • 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

=end html_DE =cut