From 0d0e5d2d264f331e7d80b9ab0b746eff8de96e5a Mon Sep 17 00:00:00 2001 From: loredo Date: Mon, 17 Apr 2017 12:04:50 +0000 Subject: [PATCH] RESIDENTS,ROOMMATE,GUEST: refactoring notification system and improved wakeuptimer git-svn-id: https://svn.fhem.de/fhem/trunk@14011 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_RESIDENTS.pm | 541 ++++++++-------------- fhem/FHEM/20_GUEST.pm | 430 +----------------- fhem/FHEM/20_ROOMMATE.pm | 426 +---------------- fhem/FHEM/RESIDENTStk.pm | 929 +++++++++++++++++++++++++++----------- 4 files changed, 897 insertions(+), 1429 deletions(-) diff --git a/fhem/FHEM/10_RESIDENTS.pm b/fhem/FHEM/10_RESIDENTS.pm index 572ad3ff9..4d85f1c85 100644 --- a/fhem/FHEM/10_RESIDENTS.pm +++ b/fhem/FHEM/10_RESIDENTS.pm @@ -1,60 +1,29 @@ +############################################################################### # $Id$ -############################################################################## -# -# 10_RESIDENTS.pm -# An FHEM Perl module to ease resident administration. -# -# Copyright by Julian Pawlowski -# e-mail: julian.pawlowski at gmail.com -# -# This file is part of fhem. -# -# Fhem 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. -# -# Fhem 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. -# -# You should have received a copy of the GNU General Public License -# along with fhem. If not, see . -# -############################################################################## - package main; - use strict; use warnings; -use Time::Local; use Data::Dumper; +use Time::Local; + require RESIDENTStk; -sub RESIDENTS_Set($@); -sub RESIDENTS_Define($$); -sub RESIDENTS_Notify($$); -sub RESIDENTS_Attr(@); -sub RESIDENTS_Undefine($$); - -################################### +# initialize ################################################################## sub RESIDENTS_Initialize($) { my ($hash) = @_; - Log3 $hash, 5, "RESIDENTS_Initialize: Entering"; - - $hash->{SetFn} = "RESIDENTS_Set"; $hash->{DefFn} = "RESIDENTS_Define"; - $hash->{NotifyFn} = "RESIDENTS_Notify"; - $hash->{AttrFn} = "RESIDENTS_Attr"; $hash->{UndefFn} = "RESIDENTS_Undefine"; + $hash->{SetFn} = "RESIDENTS_Set"; + $hash->{AttrFn} = "RESIDENTS_Attr"; + $hash->{NotifyFn} = "RESIDENTS_Notify"; + $hash->{AttrList} = -"disable:1,0 rgr_showAllStates:0,1 rgr_states:multiple-strict,home,gotosleep,asleep,awoken,absent,gone rgr_lang:EN,DE rgr_noDuration:0,1 rgr_wakeupDevice " +"disable:1,0 disabledForIntervals do_not_notify:1,0 rgr_showAllStates:0,1 rgr_states:multiple-strict,home,gotosleep,asleep,awoken,absent,gone rgr_lang:EN,DE rgr_noDuration:0,1 rgr_wakeupDevice " . $readingFnAttributes; } -################################### +# regular Fn ################################################################## sub RESIDENTS_Define($$) { my ( $hash, $def ) = @_; my $name = $hash->{NAME}; @@ -75,7 +44,7 @@ sub RESIDENTS_Define($$) { # run timers InternalTimer( gettimeofday() + 15, - "RESIDENTS_StartInternalTimers", + "RESIDENTStk_RG_StartInternalTimers", $hash, 0 ); @@ -91,88 +60,10 @@ sub RESIDENTS_Define($$) { return undef; } -################################### -sub RESIDENTS_Attr(@) { - my ( $cmd, $name, $attribute, $value ) = @_; - my $hash = $defs{$name}; - my $prefix = "rgr_"; - return unless ($init_done); - - Log3 $name, 5, "RESIDENTS $name: called function RESIDENTS_Attr()"; - - if ( $attribute eq "disable" ) { - if ( $value and $value == 1 ) { - $hash->{STATE} = "disabled"; - RESIDENTS_StopInternalTimers($hash); - } - elsif ( $cmd eq "del" or !$value ) { - evalStateFormat($hash); - RESIDENTS_StartInternalTimers( $hash, 1 ); - } - } - - elsif ( $attribute eq $prefix . "noDuration" ) { - if ($value) { - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); - } - elsif ( !$value ) { - RESIDENTS_DurationTimer($hash); - } - } - - elsif ( $attribute eq $prefix . "lang" ) { - my $lang = - $cmd eq "set" ? uc($value) : AttrVal( "global", "language", "EN" ); - - # for initial define, ensure fallback to EN - $lang = "EN" - if ( $cmd eq "init" && $lang !~ /^EN|DE$/i ); - - if ( $lang eq "DE" ) { - $attr{$name}{alias} = "Bewohner" - if ( !defined( $attr{$name}{alias} ) - || $attr{$name}{alias} eq "Residents" ); - $attr{$name}{group} = "Haus Status" - if ( !defined( $attr{$name}{group} ) - || $attr{$name}{group} eq "Home State" ); - $attr{$name}{devStateIcon} = -'.*zuhause:status_available:absent .*anwesend:status_available:absent .*abwesend:status_away_1:home .*verreist:status_standby:home .*keine:control_building_empty .*bettfertig:status_night:asleep .*schlaeft:status_night:awoken .*schläft:status_night:awoken .*aufgestanden:status_available:home .*:user_unknown:home'; - $attr{$name}{eventMap} = -"home:zuhause absent:abwesend gone:verreist none:keine gotosleep:bettfertig asleep:schläft awoken:aufgestanden"; - $attr{$name}{widgetOverride} = -"state:zuhause,bettfertig,schläft,aufgestanden,abwesend,verreist"; - } - elsif ( $lang eq "EN" ) { - $attr{$name}{alias} = "Residents" - if ( !defined( $attr{$name}{alias} ) - || $attr{$name}{alias} eq "Bewohner" ); - $attr{$name}{group} = "Home State" - if ( !defined( $attr{$name}{group} ) - || $attr{$name}{group} eq "Haus Status" ); - $attr{$name}{devStateIcon} = -'.*home:status_available:absent .*absent:status_away_1:home .*gone:status_standby:home .*none:control_building_empty .*gotosleep:status_night:asleep .*asleep:status_night:awoken .*awoken:status_available:home .*:user_unknown:home'; - delete $attr{$name}{eventMap} - if ( defined( $attr{$name}{eventMap} ) ); - delete $attr{$name}{widgetOverride} - if ( defined( $attr{$name}{widgetOverride} ) ); - } - else { - return "Unsupported language $lang"; - } - - evalStateFormat($hash); - } - - return if ( IsDisabled($name) ); - return; -} - -################################### sub RESIDENTS_Undefine($$) { my ( $hash, $name ) = @_; - RESIDENTS_StopInternalTimers($hash); + RESIDENTStk_RG_StopInternalTimers($hash); RESIDENTStk_findResidentSlaves($hash); # delete child roommates @@ -204,132 +95,6 @@ sub RESIDENTS_Undefine($$) { return undef; } -################################### -sub RESIDENTS_Notify($$) { - my ( $hash, $dev ) = @_; - my $devName = $dev->{NAME}; - my $hashName = $hash->{NAME}; - return unless ( $devName ne $hashName ); # only foreign events - return if ( IsDisabled($hashName) or IsDisabled($devName) ); - return - unless ( IsDevice( $devName, "ROOMMATE|GUEST|dummy" ) ); - - my @registeredRoommates = - split( /,/, $hash->{ROOMMATES} ) - if ( defined( $hash->{ROOMMATES} ) - && $hash->{ROOMMATES} ne "" ); - - my @registeredGuests = - split( /,/, $hash->{GUESTS} ) - if ( defined( $hash->{GUESTS} ) - && $hash->{GUESTS} ne "" ); - - my @registeredWakeupdevs = - split( /,/, $attr{$hashName}{rgr_wakeupDevice} ) - if ( defined( $attr{$hashName}{rgr_wakeupDevice} ) - && $attr{$hashName}{rgr_wakeupDevice} ne "" ); - - # process only registered ROOMMATE or GUEST devices - if ( ( @registeredRoommates && grep { /^$devName$/ } @registeredRoommates ) - || ( @registeredGuests && grep { /^$devName$/ } @registeredGuests ) ) - { - - return - if ( !$dev->{CHANGED} ); # Some previous notify deleted the array. - - readingsBeginUpdate($hash); - - foreach my $change ( @{ $dev->{CHANGED} } ) { - - Log3 $hash, 5, - "RESIDENTS " . $hashName . ": processing change $change"; - - # state changed - if ( $change !~ /:/ - || $change =~ /wayhome:/ - || $change =~ /wakeup:/ ) - { - Log3 $hash, 4, - "RESIDENTS " - . $hashName . ": " - . $devName - . ": notify about change to $change"; - - RESIDENTS_UpdateReadings($hash); - } - - # activity - if ( $change !~ /:/ ) { - - # get user realname - my $realname = - AttrVal( $devName, - AttrVal( $devName, "rr_realname", "group" ), $devName ); - $realname = - AttrVal( $devName, - AttrVal( $devName, "rg_realname", "alias" ), $devName ) - if ( $dev->{TYPE} eq "GUEST" ); - - # update statistics - readingsBulkUpdate( $hash, "lastActivity", - ReadingsVal( $devName, "state", $change ) ); - readingsBulkUpdate( $hash, "lastActivityBy", $realname ); - readingsBulkUpdate( $hash, "lastActivityByDev", $devName ); - - } - - } - - readingsEndUpdate( $hash, 1 ); - - return; - } - - # if we have registered wakeup devices - if (@registeredWakeupdevs) { - - # if this is a notification of a registered wakeup device - if ( grep { /^$devName$/ } @registeredWakeupdevs ) { - - # Some previous notify deleted the array. - return - if ( !$dev->{CHANGED} ); - - foreach my $change ( @{ $dev->{CHANGED} } ) { - RESIDENTStk_wakeupSet( $devName, $change ); - } - - return; - } - - # process sub-child notifies: *_wakeupDevice - foreach my $wakeupDev (@registeredWakeupdevs) { - - # if this is a notification of a registered sub dummy device - # of one of our wakeup devices - if ( defined( $attr{$wakeupDev}{wakeupResetSwitcher} ) - && $attr{$wakeupDev}{wakeupResetSwitcher} eq $devName - && $defs{$devName}{TYPE} eq "dummy" ) - { - - # Some previous notify deleted the array. - return - if ( !$dev->{CHANGED} ); - - foreach my $change ( @{ $dev->{CHANGED} } ) { - RESIDENTStk_wakeupSet( $wakeupDev, $change ) - if ( $change ne "off" ); - } - - last; - } - } - } - - return; -} - -################################### sub RESIDENTS_Set($@) { my ( $hash, @a ) = @_; my $name = $hash->{NAME}; @@ -570,8 +335,7 @@ sub RESIDENTS_Set($@) { # create elsif ( $a[1] eq "create" ) { if ( !defined( $a[2] ) || $a[2] !~ /^(wakeuptimer)$/i ) { - return - "Invalid 2nd argument, choose one of wakeuptimer "; + return "Invalid 2nd argument, choose one of wakeuptimer "; } elsif ( $a[2] eq "wakeuptimer" ) { my $i = "1"; @@ -639,12 +403,190 @@ sub RESIDENTS_Set($@) { return undef; } -############################################################################################################ -# -# Begin of helper functions -# -############################################################################################################ +sub RESIDENTS_Attr(@) { + my ( $cmd, $name, $attribute, $value ) = @_; + my $hash = $defs{$name}; + my $prefix = "rgr_"; + Log3 $name, 5, "RESIDENTS $name: called function RESIDENTS_Attr()"; + + if ( $attribute eq "rgr_wakeupDevice" ) { + return "Value for $attribute has invalid format" + unless ( $value =~ /^([a-zA-Z\d._]+,?)([a-zA-Z\d._]+,?)*$/ ); + + RESIDENTStk_findResidentSlaves( $hash, $value ); + } + + elsif ( !$init_done ) { + return undef; + } + + elsif ( $attribute eq "disable" ) { + if ( $value and $value == 1 ) { + $hash->{STATE} = "disabled"; + RESIDENTStk_RG_StopInternalTimers($hash); + } + elsif ( $cmd eq "del" or !$value ) { + evalStateFormat($hash); + RESIDENTStk_RG_StartInternalTimers( $hash, 1 ); + } + } + + elsif ( $attribute eq $prefix . "noDuration" ) { + if ($value) { + delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); + RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); + } + elsif ( !$value ) { + RESIDENTStk_RG_DurationTimer($hash); + } + } + + elsif ( $attribute eq $prefix . "lang" ) { + my $lang = + $cmd eq "set" ? uc($value) : AttrVal( "global", "language", "EN" ); + + # for initial define, ensure fallback to EN + $lang = "EN" + if ( $cmd eq "init" && $lang !~ /^EN|DE$/i ); + + if ( $lang eq "DE" ) { + $attr{$name}{alias} = "Bewohner" + if ( !defined( $attr{$name}{alias} ) + || $attr{$name}{alias} eq "Residents" ); + $attr{$name}{group} = "Haus Status" + if ( !defined( $attr{$name}{group} ) + || $attr{$name}{group} eq "Home State" ); + $attr{$name}{devStateIcon} = +'.*zuhause:status_available:absent .*anwesend:status_available:absent .*abwesend:status_away_1:home .*verreist:status_standby:home .*keine:control_building_empty .*bettfertig:status_night:asleep .*schlaeft:status_night:awoken .*schläft:status_night:awoken .*aufgestanden:status_available:home .*:user_unknown:home'; + $attr{$name}{eventMap} = +"home:zuhause absent:abwesend gone:verreist none:keine gotosleep:bettfertig asleep:schläft awoken:aufgestanden"; + $attr{$name}{widgetOverride} = +"state:zuhause,bettfertig,schläft,aufgestanden,abwesend,verreist"; + } + elsif ( $lang eq "EN" ) { + $attr{$name}{alias} = "Residents" + if ( !defined( $attr{$name}{alias} ) + || $attr{$name}{alias} eq "Bewohner" ); + $attr{$name}{group} = "Home State" + if ( !defined( $attr{$name}{group} ) + || $attr{$name}{group} eq "Haus Status" ); + $attr{$name}{devStateIcon} = +'.*home:status_available:absent .*absent:status_away_1:home .*gone:status_standby:home .*none:control_building_empty .*gotosleep:status_night:asleep .*asleep:status_night:awoken .*awoken:status_available:home .*:user_unknown:home'; + delete $attr{$name}{eventMap} + if ( defined( $attr{$name}{eventMap} ) ); + delete $attr{$name}{widgetOverride} + if ( defined( $attr{$name}{widgetOverride} ) ); + } + else { + return "Unsupported language $lang"; + } + + evalStateFormat($hash); + } + + return undef; +} + +sub RESIDENTS_Notify($$) { + my ( $hash, $dev ) = @_; + my $name = $hash->{NAME}; + my $prefix = RESIDENTStk_GetPrefixFromType($name); + my $devName = $dev->{NAME}; + my $devType = GetType($devName); + return "" if ( IsDisabled($name) or IsDisabled($devName) ); + + # process only ROOMMATE or GUEST devices + if ( $devType =~ /^ROOMMATE|GUEST$/ ) { + + my $events = deviceEvents( $dev, 1 ); + return "" unless ($events); + + readingsBeginUpdate($hash); + + foreach my $event ( @{$events} ) { + next unless ( defined($event) ); + + Log3 $hash, 5, "RESIDENTS " . $name . ": processing event - $event"; + + # state changed + if ( $event =~ /^state:/ + || $event =~ /^wayhome:/ + || $event =~ /^wakeup:/ ) + { + RESIDENTS_UpdateReadings($hash); + } + + # activity + if ( $event =~ /^state:/ ) { + + # get user realname + my $aliasAttr = "group"; + $aliasAttr = "alias" if ( $prefix eq "rg_" ); + my $realname = + AttrVal( $devName, + AttrVal( $devName, $prefix . "realname", $aliasAttr ), + $devName ); + + # update statistics + readingsBulkUpdate( $hash, "lastActivity", + ReadingsVal( $devName, "state", $event ) ); + readingsBulkUpdate( $hash, "lastActivityBy", $realname ); + readingsBulkUpdate( $hash, "lastActivityByDev", $devName ); + + } + } + + readingsEndUpdate( $hash, 1 ); + + return ""; + } + + delete $dev->{CHANGEDWITHSTATE}; + my $events = deviceEvents( $dev, 1 ); + return "" unless ($events); + + # process wakeup devices + my @registeredWakeupdevs = + split( ',', AttrVal( $name, $prefix . "wakeupDevice", "" ) ); + if (@registeredWakeupdevs) { + + # if this is a notification of a registered wakeup device + if ( grep { m/^$devName$/ } @registeredWakeupdevs ) { + + foreach my $event ( @{$events} ) { + next unless ( defined($event) ); + RESIDENTStk_wakeupSet( $devName, $event ); + } + + return ""; + } + + # process sub-child notifies: *_wakeupDevice + foreach my $wakeupDev (@registeredWakeupdevs) { + + # if this is a notification of a registered sub dummy device + # of one of our wakeup devices + if ( AttrVal( $wakeupDev, "wakeupResetSwitcher", "" ) eq $devName + && IsDevice( $devName, "dummy" ) ) + { + foreach my $event ( @{$events} ) { + next unless ( defined($event) ); + RESIDENTStk_wakeupSet( $wakeupDev, $event ) + unless ( $event =~ /^(?:state:\s*)?off$/i ); + } + + return ""; + } + } + + return ""; + } + + return ""; +} + +# module Fn #################################################################### sub RESIDENTS_UpdateReadings (@) { my ($hash) = @_; my $name = $hash->{NAME}; @@ -1511,96 +1453,7 @@ sub RESIDENTS_UpdateReadings (@) { } # calculate duration timers - RESIDENTS_DurationTimer( $hash, 1 ); -} - -sub RESIDENTS_DurationTimer($;$) { - my ( $mHash, @a ) = @_; - my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; - my $name = $hash->{NAME}; - my $silent = ( defined( $a[0] ) && $a[0] eq "1" ) ? 1 : 0; - my $timestampNow = gettimeofday(); - my $diff; - my $durPresence = "0"; - my $durAbsence = "0"; - my $durSleep = "0"; - my $noDuration = AttrVal( $name, "rgr_noDuration", 0 ); - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); - - return if ( IsDisabled($name) || $noDuration ); - - # presence timer - if ( ReadingsVal( $name, "presence", "absent" ) eq "present" - && ReadingsVal( $name, "lastArrival", "-" ) ne "-" ) - { - $durPresence = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastArrival", "" ) ); - } - - # absence timer - if ( ReadingsVal( $name, "presence", "present" ) eq "absent" - && ReadingsVal( $name, "lastDeparture", "-" ) ne "-" ) - { - $durAbsence = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastDeparture", "" ) ); - } - - # sleep timer - if ( ReadingsVal( $name, "state", "home" ) eq "asleep" - && ReadingsVal( $name, "lastSleep", "-" ) ne "-" ) - { - $durSleep = - $timestampNow - time_str2num( ReadingsVal( $name, "lastSleep", "" ) ); - } - - my $durPresence_hr = - ( $durPresence > 0 ) - ? RESIDENTStk_sec2time($durPresence) - : "00:00:00"; - my $durPresence_cr = - ( $durPresence > 60 ) ? int( $durPresence / 60 + 0.5 ) : 0; - my $durAbsence_hr = - ( $durAbsence > 0 ) ? RESIDENTStk_sec2time($durAbsence) : "00:00:00"; - my $durAbsence_cr = - ( $durAbsence > 60 ) ? int( $durAbsence / 60 + 0.5 ) : 0; - my $durSleep_hr = - ( $durSleep > 0 ) ? RESIDENTStk_sec2time($durSleep) : "00:00:00"; - my $durSleep_cr = ( $durSleep > 60 ) ? int( $durSleep / 60 + 0.5 ) : 0; - - readingsBeginUpdate($hash) if ( !$silent ); - readingsBulkUpdateIfChanged( $hash, "durTimerPresence_cr", - $durPresence_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerPresence", $durPresence_hr ); - readingsBulkUpdateIfChanged( $hash, "durTimerAbsence_cr", $durAbsence_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerAbsence", $durAbsence_hr ); - readingsBulkUpdateIfChanged( $hash, "durTimerSleep_cr", $durSleep_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerSleep", $durSleep_hr ); - readingsEndUpdate( $hash, 1 ) if ( !$silent ); - - $hash->{DURATIONTIMER} = $timestampNow + 60; - - RESIDENTStk_InternalTimer( "DurationTimer", $hash->{DURATIONTIMER}, - "RESIDENTS_DurationTimer", $hash, 1 ); - - return undef; -} - -sub RESIDENTS_StartInternalTimers($$) { - my ($hash) = @_; - - RESIDENTS_DurationTimer($hash); -} - -sub RESIDENTS_StopInternalTimers($) { - my ($hash) = @_; - - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); + RESIDENTStk_RG_DurationTimer( $hash, 1 ); } 1; diff --git a/fhem/FHEM/20_GUEST.pm b/fhem/FHEM/20_GUEST.pm index 3f85b739c..e94b94cd4 100644 --- a/fhem/FHEM/20_GUEST.pm +++ b/fhem/FHEM/20_GUEST.pm @@ -1,60 +1,29 @@ +############################################################################### # $Id$ -############################################################################## -# -# 20_GUEST.pm -# Submodule of 10_RESIDENTS. -# -# Copyright by Julian Pawlowski -# e-mail: julian.pawlowski at gmail.com -# -# This file is part of fhem. -# -# Fhem 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. -# -# Fhem 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. -# -# You should have received a copy of the GNU General Public License -# along with fhem. If not, see . -# -############################################################################## - package main; - use strict; use warnings; -use Time::Local; use Data::Dumper; +use Time::Local; + require RESIDENTStk; -sub GUEST_Set($@); -sub GUEST_Define($$); -sub GUEST_Notify($$); -sub GUEST_Attr(@); -sub GUEST_Undefine($$); - -################################### +# initialize ################################################################## sub GUEST_Initialize($) { my ($hash) = @_; - Log3 $hash, 5, "GUEST_Initialize: Entering"; - - $hash->{SetFn} = "GUEST_Set"; $hash->{DefFn} = "GUEST_Define"; - $hash->{NotifyFn} = "GUEST_Notify"; - $hash->{AttrFn} = "GUEST_Attr"; $hash->{UndefFn} = "GUEST_Undefine"; + $hash->{SetFn} = "GUEST_Set"; + $hash->{AttrFn} = "RESIDENTStk_RG_Attr"; + $hash->{NotifyFn} = "RESIDENTStk_RG_Notify"; + $hash->{AttrList} = -"disable:1,0 rg_locationHome rg_locationWayhome rg_locationUnderway rg_autoGoneAfter:0,12,16,24,26,28,30,36,48,60 rg_showAllStates:0,1 rg_realname:group,alias rg_states:multiple-strict,home,gotosleep,asleep,awoken,absent,gone rg_locations rg_moods rg_moodDefault rg_moodSleepy rg_noDuration:0,1 rg_wakeupDevice rg_geofenceUUIDs rg_presenceDevices rg_lang:EN,DE " +"disable:1,0 disabledForIntervals do_not_notify:1,0 rg_locationHome rg_locationWayhome rg_locationUnderway rg_autoGoneAfter:0,12,16,24,26,28,30,36,48,60 rg_showAllStates:0,1 rg_realname:group,alias rg_states:multiple-strict,home,gotosleep,asleep,awoken,absent,gone rg_locations rg_moods rg_moodDefault rg_moodSleepy rg_noDuration:0,1 rg_wakeupDevice rg_geofenceUUIDs rg_presenceDevices rg_lang:EN,DE " . $readingFnAttributes; } -################################### +# regular Fn ################################################################## sub GUEST_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); @@ -69,6 +38,8 @@ sub GUEST_Define($$) { return $msg; } + $hash->{NOTIFYDEV} = ""; + $hash->{RESIDENTGROUPS} = defined( $a[2] ) ? $a[2] : ""; if ( defined( $hash->{RESIDENTGROUPS} ) ) { foreach ( split( /,/, $hash->{RESIDENTGROUPS} ) ) { @@ -111,7 +82,11 @@ sub GUEST_Define($$) { readingsEndUpdate( $hash, 1 ); # run timers - InternalTimer( gettimeofday() + 15, "GUEST_StartInternalTimers", $hash, 0 ); + InternalTimer( + gettimeofday() + 15, + "RESIDENTStk_RG_StartInternalTimers", + $hash, 0 + ); # Injecting AttrFn for use with RESIDENTS Toolkit if ( !defined( $modules{dummy}{AttrFn} ) ) { @@ -127,86 +102,10 @@ sub GUEST_Define($$) { return undef; } -################################### -sub GUEST_Attr(@) { - my ( $cmd, $name, $attribute, $value ) = @_; - my $hash = $defs{$name}; - my $prefix = "rg_"; - return unless ($init_done); - - Log3 $name, 5, "GUEST $name: called function GUEST_Attr()"; - - if ( $attribute eq "disable" ) { - if ( $value and $value == 1 ) { - $hash->{STATE} = "disabled"; - GUEST_StopInternalTimers($hash); - } - elsif ( $cmd eq "del" or !$value ) { - evalStateFormat($hash); - GUEST_StartInternalTimers( $hash, 1 ); - } - } - - elsif ( $attribute eq $prefix . "autoGoneAfter" ) { - if ($value) { - GUEST_AutoGone($hash); - } - elsif ( !$value ) { - delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); - RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); - } - } - - elsif ( $attribute eq $prefix . "noDuration" ) { - if ($value) { - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); - } - elsif ( !$value ) { - GUEST_DurationTimer($hash); - } - } - - elsif ( $attribute eq $prefix . "lang" ) { - my $lang = - $cmd eq "set" ? uc($value) : AttrVal( "global", "language", "EN" ); - - # for initial define, ensure fallback to EN - $lang = "EN" - if ( $cmd eq "init" && $lang !~ /^EN|DE$/i ); - - if ( $lang eq "DE" ) { - $attr{$name}{devStateIcon} = -'.*zuhause:user_available:absent .*anwesend:user_available:absent .*abwesend:user_away:home .*keiner:control_building_empty:home .*bettfertig:scene_toilet:asleep .*schlaeft:scene_sleeping:awoken .*schläft:scene_sleeping:awoken .*aufgestanden:scene_sleeping_alternat:home .*:user_unknown:home'; - $attr{$name}{eventMap} = -"home:zuhause absent:abwesend none:keiner gotosleep:bettfertig asleep:schläft awoken:aufgestanden"; - $attr{$name}{widgetOverride} = - "state:zuhause,bettfertig,schläft,aufgestanden,abwesend,keiner"; - } - elsif ( $lang eq "EN" ) { - $attr{$name}{devStateIcon} = -'.*home:user_available:absent .*absent:user_away:home .*none:control_building_empty:home .*gotosleep:scene_toilet:asleep .*asleep:scene_sleeping:awoken .*awoken:scene_sleeping_alternat:home .*:user_unknown:home'; - delete $attr{$name}{eventMap} - if ( defined( $attr{$name}{eventMap} ) ); - delete $attr{$name}{widgetOverride} - if ( defined( $attr{$name}{widgetOverride} ) ); - } - else { - return "Unsupported language $lang"; - } - - evalStateFormat($hash); - } - - return if ( IsDisabled($name) ); - return; -} - -################################### sub GUEST_Undefine($$) { my ( $hash, $name ) = @_; - GUEST_StopInternalTimers($hash); + RESIDENTStk_RG_StopInternalTimers($hash); if ( defined( $hash->{RESIDENTGROUPS} ) ) { my $old = $hash->{RESIDENTGROUPS}; @@ -223,147 +122,8 @@ sub GUEST_Undefine($$) { return undef; } -################################### -sub GUEST_Notify($$) { - my ( $hash, $dev ) = @_; - my $devName = $dev->{NAME}; - my $hashName = $hash->{NAME}; - return if ( IsDisabled($hashName) or IsDisabled($devName) ); +sub GUEST_Set($@); - # process global:INITIALIZED - if ( $dev->{NAME} eq "global" - && grep( m/^INITIALIZED$/, @{ $dev->{CHANGED} } ) ) - { - - my @registeredWakeupdevs = - split( /,/, AttrVal( $hashName, "rg_wakeupDevice", 0 ) ); - - # if we have registered wakeup devices - if (@registeredWakeupdevs) { - - # look for at devices for each wakeup device - foreach my $wakeupDev (@registeredWakeupdevs) { - my $wakeupAtdevice = AttrVal( $wakeupDev, "wakeupAtdevice", 0 ); - - # make sure computeAfterInit is set at at-device - # and re-calculate on our own this time - if ( IsDevice( $wakeupAtdevice, "at" ) - && AttrVal( $wakeupAtdevice, "computeAfterInit", 0 ) ne - "1" ) - { - Log3 $wakeupDev, 3, -"RESIDENTStk $wakeupDev: Correcting '$wakeupAtdevice' attribute computeAfterInit required for correct recalculation after reboot"; - fhem "attr $wakeupAtdevice computeAfterInit 1"; - - my $command; - ( $command, undef ) = - split( "[ \t]+", $defs{$wakeupAtdevice}{DEF}, 2 ); - $command =~ s/^[*+]//; - return at_Set( $defs{$wakeupAtdevice}, - ( $wakeupAtdevice, "modifyTimeSpec", $command ) ); - } - } - } - } - - # process child notifies - elsif ( $devName ne $hashName ) { - my @registeredWakeupdevs = - split( ',', AttrVal( $hashName, "rg_wakeupDevice", "" ) ); - my @presenceDevices = - split( ',', AttrVal( $hashName, "rg_presenceDevices", "" ) ); - - # if we have registered wakeup devices - if (@registeredWakeupdevs) { - - # if this is a notification of a registered wakeup device - if ( grep { /^$devName$/ } @registeredWakeupdevs ) { - - # Some previous notify deleted the array. - return - if ( !$dev->{CHANGED} ); - - foreach my $change ( @{ $dev->{CHANGED} } ) { - RESIDENTStk_wakeupSet( $devName, $change ); - } - - return; - } - - # process sub-child notifies: *_wakeupDevice - foreach my $wakeupDev (@registeredWakeupdevs) { - - # if this is a notification of a registered sub dummy device - # of one of our wakeup devices - if ( - AttrVal( $wakeupDev, "wakeupResetSwitcher", "" ) eq $devName - && $dev->{TYPE} eq "dummy" ) - { - - # Some previous notify deleted the array. - return - if ( !$dev->{CHANGED} ); - - foreach my $change ( @{ $dev->{CHANGED} } ) { - RESIDENTStk_wakeupSet( $wakeupDev, $change ) - if ( $change ne "off" ); - } - - last; - } - } - } - - # process PRESENCE - if ( @presenceDevices - && grep { /^[\s\t ]*$devName(:[A-Za-z\d_\.\-\/]*)?[\s\t ]*$/ } - @presenceDevices ) - { - - my $counter = { - absent => 0, - present => 0, - }; - - foreach (@presenceDevices) { - my $r = "presence"; - my $d = $_; - if ( $d =~ -m/^[\s\t ]*([A-Za-z\d_\.\-\/]+):([A-Za-z\d_\.\-\/]+)?[\s\t ]*$/ - ) - { - $d = $1; - $r = $2; - } - - my $presenceState = - ReadingsVal( $d, $r, ReadingsVal( $d, "state", "" ) ); - next - unless ( $presenceState =~ -m/^(0|false|absent|disappeared|unavailable|unreachable|disconnected)|(1|true|present|appeared|available|reachable|connected|)$/i - ); - - $counter->{absent}++ if ($1); - $counter->{present}++ if ($2); - } - - if ( $counter->{absent} && !$counter->{present} ) { - Log3 $hashName, 4, - "GUEST $hashName: Syncing status with $devName = absent"; - fhem "set $hashName:FILTER=presence=present absent"; - } - elsif ( $counter->{present} ) { - Log3 $hashName, 4, - "GUEST $hashName: Syncing status with $devName = present"; - fhem "set $hashName:FILTER=presence=absent home"; - } - } - } - - return; -} - -################################### sub GUEST_Set($@) { my ( $hash, @a ) = @_; my $name = $hash->{NAME}; @@ -728,13 +488,13 @@ sub GUEST_Set($@) { } # calculate duration timers - GUEST_DurationTimer( $hash, $silent ); + RESIDENTStk_RG_DurationTimer( $hash, $silent ); readingsEndUpdate( $hash, 1 ); # enable or disable AutoGone timer if ( $newstate eq "absent" ) { - GUEST_AutoGone($hash); + RESIDENTStk_RG_AutoGone($hash); } elsif ( $state eq "absent" ) { delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); @@ -888,7 +648,7 @@ sub GUEST_Set($@) { fhem "attr $wakeuptimerName room " . $attr{$name}{room} if ( defined( $attr{$name}{room} ) ); fhem -"attr $wakeuptimerName setList nextRun:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 reset:noArg trigger:noArg start:noArg stop:noArg end:noArg wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:,andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3"; +"attr $wakeuptimerName setList nextRun:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 reset:noArg trigger:noArg start:noArg stop:noArg end:noArg wakeupOffset:slider,0,1,120 wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:,andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3"; fhem "attr $wakeuptimerName userattr wakeupUserdevice"; fhem "attr $wakeuptimerName sortby " . $sortby if ($sortby); @@ -960,132 +720,7 @@ sub GUEST_Set($@) { return undef; } -############################################################################################################ -# -# Begin of helper functions -# -############################################################################################################ - -################################### -sub GUEST_AutoGone($;$) { - my ( $mHash, @a ) = @_; - my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; - my $name = $hash->{NAME}; - my $autoGoneAfter = AttrVal( $hash->{NAME}, "rg_autoGoneAfter", 16 ); - delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); - - RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); - - return if ( IsDisabled($name) ); - - if ( ReadingsVal( $name, "state", "home" ) eq "absent" ) { - my ( $date, $time, $y, $m, $d, $hour, $min, $sec, $timestamp, - $timeDiff ); - my $timestampNow = gettimeofday(); - - ( $date, $time ) = split( ' ', $hash->{READINGS}{state}{TIME} ); - ( $y, $m, $d ) = split( '-', $date ); - ( $hour, $min, $sec ) = split( ':', $time ); - $m -= 01; - $timestamp = timelocal( $sec, $min, $hour, $d, $m, $y ); - $timeDiff = $timestampNow - $timestamp; - - if ( $timeDiff >= $autoGoneAfter * 3600 ) { - Log3 $name, 3, - "GUEST $name: AutoGone timer changed state to 'gone'"; - GUEST_Set( $hash, $name, "silentSet", "state", "gone" ); - } - else { - my $runtime = $timestamp + $autoGoneAfter * 3600; - $hash->{AUTOGONE} = $runtime; - Log3 $name, 4, "GUEST $name: AutoGone timer scheduled: $runtime"; - RESIDENTStk_InternalTimer( "AutoGone", $runtime, "GUEST_AutoGone", - $hash, 1 ); - } - } - - return undef; -} - -################################### -sub GUEST_DurationTimer($;$) { - my ( $mHash, @a ) = @_; - my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; - my $name = $hash->{NAME}; - my $state = ReadingsVal( $name, "state", "initialized" ); - my $silent = ( defined( $a[0] ) && $a[0] eq "1" ) ? 1 : 0; - my $timestampNow = gettimeofday(); - my $diff; - my $durPresence = "0"; - my $durAbsence = "0"; - my $durSleep = "0"; - my $noDuration = AttrVal( $name, "rg_noDuration", 0 ); - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); - - return if ( IsDisabled($name) || $noDuration ); - - # presence timer - if ( ReadingsVal( $name, "presence", "absent" ) eq "present" - && ReadingsVal( $name, "lastArrival", "-" ) ne "-" ) - { - $durPresence = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastArrival", "" ) ); - } - - # absence timer - if ( ReadingsVal( $name, "presence", "present" ) eq "absent" - && ReadingsVal( $name, "lastDeparture", "-" ) ne "-" ) - { - $durAbsence = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastDeparture", "" ) ); - } - - # sleep timer - if ( ReadingsVal( $name, "state", "home" ) eq "asleep" - && ReadingsVal( $name, "lastSleep", "-" ) ne "-" ) - { - $durSleep = - $timestampNow - time_str2num( ReadingsVal( $name, "lastSleep", "" ) ); - } - - my $durPresence_hr = - ( $durPresence > 0 ) - ? RESIDENTStk_sec2time($durPresence) - : "00:00:00"; - my $durPresence_cr = - ( $durPresence > 60 ) ? int( $durPresence / 60 + 0.5 ) : 0; - my $durAbsence_hr = - ( $durAbsence > 0 ) ? RESIDENTStk_sec2time($durAbsence) : "00:00:00"; - my $durAbsence_cr = - ( $durAbsence > 60 ) ? int( $durAbsence / 60 + 0.5 ) : 0; - my $durSleep_hr = - ( $durSleep > 0 ) ? RESIDENTStk_sec2time($durSleep) : "00:00:00"; - my $durSleep_cr = ( $durSleep > 60 ) ? int( $durSleep / 60 + 0.5 ) : 0; - - readingsBeginUpdate($hash) if ( !$silent ); - readingsBulkUpdateIfChanged( $hash, "durTimerPresence_cr", - $durPresence_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerPresence", $durPresence_hr ); - readingsBulkUpdateIfChanged( $hash, "durTimerAbsence_cr", $durAbsence_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerAbsence", $durAbsence_hr ); - readingsBulkUpdateIfChanged( $hash, "durTimerSleep_cr", $durSleep_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerSleep", $durSleep_hr ); - readingsEndUpdate( $hash, 1 ) if ( !$silent ); - - $hash->{DURATIONTIMER} = $timestampNow + 60; - - RESIDENTStk_InternalTimer( "DurationTimer", $hash->{DURATIONTIMER}, - "GUEST_DurationTimer", $hash, 1 ) - if ( $state ne "none" ); - - return undef; -} - -################################### +# module Fn #################################################################### sub GUEST_SetLocation($$$;$$$$$$) { my ( $name, $location, $trigger, $id, $time, $lat, $long, $address, $device ) = @_; @@ -1272,25 +907,6 @@ sub GUEST_SetLocation($$$;$$$$$$) { } -################################### -sub GUEST_StartInternalTimers($$) { - my ($hash) = @_; - - GUEST_AutoGone($hash); - GUEST_DurationTimer($hash); -} - -################################### -sub GUEST_StopInternalTimers($) { - my ($hash) = @_; - - delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - - RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); -} - 1; =pod diff --git a/fhem/FHEM/20_ROOMMATE.pm b/fhem/FHEM/20_ROOMMATE.pm index 38d8a0e50..fa47e3dbe 100644 --- a/fhem/FHEM/20_ROOMMATE.pm +++ b/fhem/FHEM/20_ROOMMATE.pm @@ -1,60 +1,29 @@ +############################################################################### # $Id$ -############################################################################## -# -# 20_ROOMMATE.pm -# Submodule of 10_RESIDENTS. -# -# Copyright by Julian Pawlowski -# e-mail: julian.pawlowski at gmail.com -# -# This file is part of fhem. -# -# Fhem 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. -# -# Fhem 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. -# -# You should have received a copy of the GNU General Public License -# along with fhem. If not, see . -# -############################################################################## - package main; - use strict; use warnings; -use Time::Local; use Data::Dumper; +use Time::Local; + require RESIDENTStk; -sub ROOMMATE_Set($@); -sub ROOMMATE_Define($$); -sub ROOMMATE_Notify($$); -sub ROOMMATE_Attr(@); -sub ROOMMATE_Undefine($$); - -################################### +# initialize ################################################################## sub ROOMMATE_Initialize($) { my ($hash) = @_; - Log3 $hash, 5, "ROOMMATE_Initialize: Entering"; - - $hash->{SetFn} = "ROOMMATE_Set"; $hash->{DefFn} = "ROOMMATE_Define"; - $hash->{NotifyFn} = "ROOMMATE_Notify"; - $hash->{AttrFn} = "ROOMMATE_Attr"; $hash->{UndefFn} = "ROOMMATE_Undefine"; + $hash->{SetFn} = "ROOMMATE_Set"; + $hash->{AttrFn} = "RESIDENTStk_RG_Attr"; + $hash->{NotifyFn} = "RESIDENTStk_RG_Notify"; + $hash->{AttrList} = -"disable:1,0 rr_locationHome rr_locationWayhome rr_locationUnderway rr_autoGoneAfter:0,12,16,24,26,28,30,36,48,60 rr_showAllStates:0,1 rr_realname:group,alias rr_states:multiple-strict,home,gotosleep,asleep,awoken,absent,gone rr_locations rr_moods rr_moodDefault rr_moodSleepy rr_passPresenceTo rr_noDuration:0,1 rr_wakeupDevice rr_geofenceUUIDs rr_presenceDevices rr_lang:EN,DE " +"disable:1,0 disabledForIntervals do_not_notify:1,0 rr_locationHome rr_locationWayhome rr_locationUnderway rr_autoGoneAfter:0,12,16,24,26,28,30,36,48,60 rr_showAllStates:0,1 rr_realname:group,alias rr_states:multiple-strict,home,gotosleep,asleep,awoken,absent,gone rr_locations rr_moods rr_moodDefault rr_moodSleepy rr_passPresenceTo rr_noDuration:0,1 rr_wakeupDevice rr_geofenceUUIDs rr_presenceDevices rr_lang:EN,DE " . $readingFnAttributes; } -################################### +# regular Fn ################################################################## sub ROOMMATE_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); @@ -70,6 +39,8 @@ sub ROOMMATE_Define($$) { return $msg; } + $hash->{NOTIFYDEV} = ""; + $hash->{RESIDENTGROUPS} = defined( $a[2] ) ? $a[2] : ""; if ( defined( $hash->{RESIDENTGROUPS} ) ) { foreach ( split( /,/, $hash->{RESIDENTGROUPS} ) ) { @@ -112,7 +83,7 @@ sub ROOMMATE_Define($$) { # run timers InternalTimer( gettimeofday() + 15, - "ROOMMATE_StartInternalTimers", + "RESIDENTStk_RG_StartInternalTimers", $hash, 0 ); @@ -130,86 +101,10 @@ sub ROOMMATE_Define($$) { return undef; } -################################### -sub ROOMMATE_Attr(@) { - my ( $cmd, $name, $attribute, $value ) = @_; - my $hash = $defs{$name}; - my $prefix = "rr_"; - return unless ($init_done); - - Log3 $name, 5, "ROOMMATE $name: called function ROOMMATE_Attr()"; - - if ( $attribute eq "disable" ) { - if ( $value and $value == 1 ) { - $hash->{STATE} = "disabled"; - ROOMMATE_StopInternalTimers($hash); - } - elsif ( $cmd eq "del" or !$value ) { - evalStateFormat($hash); - ROOMMATE_StartInternalTimers( $hash, 1 ); - } - } - - elsif ( $attribute eq $prefix . "autoGoneAfter" ) { - if ($value) { - ROOMMATE_AutoGone($hash); - } - elsif ( !$value ) { - delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); - RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); - } - } - - elsif ( $attribute eq $prefix . "noDuration" ) { - if ($value) { - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); - } - elsif ( !$value ) { - ROOMMATE_DurationTimer($hash); - } - } - - elsif ( $attribute eq $prefix . "lang" ) { - my $lang = - $cmd eq "set" ? uc($value) : AttrVal( "global", "language", "EN" ); - - # for initial define, ensure fallback to EN - $lang = "EN" - if ( $cmd eq "init" && $lang !~ /^EN|DE$/i ); - - if ( $lang eq "DE" ) { - $attr{$name}{devStateIcon} = -'.*zuhause:user_available:absent .*anwesend:user_available:absent .*abwesend:user_away:home .*verreist:user_ext_away:home .*bettfertig:scene_toilet:asleep .*schlaeft:scene_sleeping:awoken .*schläft:scene_sleeping:awoken .*aufgestanden:scene_sleeping_alternat:home .*:user_unknown:home'; - $attr{$name}{eventMap} = -"home:zuhause absent:abwesend gone:verreist gotosleep:bettfertig asleep:schläft awoken:aufgestanden"; - $attr{$name}{widgetOverride} = -"state:zuhause,bettfertig,schläft,aufgestanden,abwesend,verreist"; - } - elsif ( $lang eq "EN" ) { - $attr{$name}{devStateIcon} = -'.*home:user_available:absent .*absent:user_away:home .*gone:user_ext_away:home .*gotosleep:scene_toilet:asleep .*asleep:scene_sleeping:awoken .*awoken:scene_sleeping_alternat:home .*:user_unknown:home'; - delete $attr{$name}{eventMap} - if ( defined( $attr{$name}{eventMap} ) ); - delete $attr{$name}{widgetOverride} - if ( defined( $attr{$name}{widgetOverride} ) ); - } - else { - return "Unsupported language $lang"; - } - - evalStateFormat($hash); - } - - return if ( IsDisabled($name) ); - return; -} - -################################### sub ROOMMATE_Undefine($$) { my ( $hash, $name ) = @_; - ROOMMATE_StopInternalTimers($hash); + RESIDENTStk_RG_StopInternalTimers($hash); if ( defined( $hash->{RESIDENTGROUPS} ) ) { my $old = $hash->{RESIDENTGROUPS}; @@ -226,149 +121,8 @@ sub ROOMMATE_Undefine($$) { return undef; } -################################### -sub ROOMMATE_Notify($$) { - my ( $hash, $dev ) = @_; - my $devName = $dev->{NAME}; - my $hashName = $hash->{NAME}; - return if ( IsDisabled($hashName) or IsDisabled($devName) ); +sub ROOMMATE_Set($@); - # process global:INITIALIZED - if ( $dev->{NAME} eq "global" - && grep( m/^INITIALIZED$/, @{ $dev->{CHANGED} } ) ) - { - - my @registeredWakeupdevs = - split( /,/, AttrVal( $hashName, "rr_wakeupDevice", 0 ) ); - - # if we have registered wakeup devices - if (@registeredWakeupdevs) { - - # look for at devices for each wakeup device - foreach my $wakeupDev (@registeredWakeupdevs) { - my $wakeupAtdevice = AttrVal( $wakeupDev, "wakeupAtdevice", 0 ); - - # make sure computeAfterInit is set at at-device - # and re-calculate on our own this time - if ( IsDevice( $wakeupAtdevice, "at" ) - && AttrVal( $wakeupAtdevice, "computeAfterInit", 0 ) ne - "1" ) - { - Log3 $wakeupDev, 3, -"RESIDENTStk $wakeupDev: Correcting '$wakeupAtdevice' attribute computeAfterInit required for correct recalculation after reboot"; - fhem "attr $wakeupAtdevice computeAfterInit 1"; - - my $command; - ( $command, undef ) = - split( "[ \t]+", $defs{$wakeupAtdevice}{DEF}, 2 ); - $command =~ s/^[*+]//; - return at_Set( $defs{$wakeupAtdevice}, - ( $wakeupAtdevice, "modifyTimeSpec", $command ) ); - } - } - } - } - - # process child notifies - elsif ( $devName ne $hashName ) { - my @registeredWakeupdevs = - split( ',', AttrVal( $hashName, "rr_wakeupDevice", "" ) ); - my @presenceDevices = - split( ',', AttrVal( $hashName, "rr_presenceDevices", "" ) ); - - # if we have registered wakeup devices - if (@registeredWakeupdevs) { - - # if this is a notification of a registered wakeup device - if ( grep { /^$devName$/ } @registeredWakeupdevs ) { - - # Some previous notify deleted the array. - return - if ( !$dev->{CHANGED} ); - - foreach my $change ( @{ $dev->{CHANGED} } ) { - RESIDENTStk_wakeupSet( $devName, $change ); - } - - return; - } - - # process sub-child notifies: *_wakeupDevice - foreach my $wakeupDev (@registeredWakeupdevs) { - - # if this is a notification of a registered sub dummy device - # of one of our wakeup devices - if ( - AttrVal( $wakeupDev, "wakeupResetSwitcher", "" ) eq $devName - && $dev->{TYPE} eq "dummy" ) - { - - # Some previous notify deleted the array. - return - if ( !$dev->{CHANGED} ); - - foreach my $change ( @{ $dev->{CHANGED} } ) { - RESIDENTStk_wakeupSet( $wakeupDev, $change ) - if ( $change ne "off" ); - } - - last; - } - } - } - - # process PRESENCE - if ( @presenceDevices - && grep { /^[\s\t ]*$devName(:[A-Za-z\d_\.\-\/]*)?[\s\t ]*$/ } - @presenceDevices ) - { - - my $counter = { - absent => 0, - present => 0, - }; - - for (@presenceDevices) { - my $r = "presence"; - my $d = $_; - if ( $d =~ -m/^[\s\t ]*([A-Za-z\d_\.\-\/]+):([A-Za-z\d_\.\-\/]+)?[\s\t ]*$/ - ) - { - $d = $1; - $r = $2; - } - - my $presenceState = - ReadingsVal( $d, $r, ReadingsVal( $d, "state", "" ) ); - next - unless ( $presenceState =~ -m/^(0|false|absent|disappeared|unavailable|unreachable|disconnected)|(1|true|present|appeared|available|reachable|connected|)$/i - ); - - $counter->{absent}++ if ($1); - $counter->{present}++ if ($2); - } - - if ( $counter->{absent} && !$counter->{present} ) { - Log3 $hashName, 4, - "ROOMMATE $hashName: " - . "Syncing status with $devName = absent"; - fhem "set $hashName:FILTER=presence=present absent"; - } - elsif ( $counter->{present} ) { - Log3 $hashName, 4, - "ROOMMATE $hashName: " - . "Syncing status with $devName = present"; - fhem "set $hashName:FILTER=presence=absent home"; - } - } - } - - return; -} - -################################### sub ROOMMATE_Set($@) { my ( $hash, @a ) = @_; my $name = $hash->{NAME}; @@ -708,13 +462,13 @@ sub ROOMMATE_Set($@) { } # calculate duration timers - ROOMMATE_DurationTimer( $hash, $silent ); + RESIDENTStk_RG_DurationTimer( $hash, $silent ); readingsEndUpdate( $hash, 1 ); # enable or disable AutoGone timer if ( $newstate eq "absent" ) { - ROOMMATE_AutoGone($hash); + RESIDENTStk_RG_AutoGone($hash); } elsif ( $state eq "absent" ) { delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); @@ -870,7 +624,7 @@ sub ROOMMATE_Set($@) { fhem "attr $wakeuptimerName room " . $attr{$name}{room} if ( defined( $attr{$name}{room} ) ); fhem -"attr $wakeuptimerName setList nextRun:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 reset:noArg trigger:noArg start:noArg stop:noArg end:noArg wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:,andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3"; +"attr $wakeuptimerName setList nextRun:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 reset:noArg trigger:noArg start:noArg stop:noArg end:noArg wakeupOffset:slider,0,1,120 wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:,andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3"; fhem "attr $wakeuptimerName userattr wakeupUserdevice"; fhem "attr $wakeuptimerName sortby " . $sortby if ($sortby); @@ -942,130 +696,7 @@ sub ROOMMATE_Set($@) { return undef; } -############################################################################################################ -# -# Begin of helper functions -# -############################################################################################################ - -################################### -sub ROOMMATE_AutoGone($;$) { - my ( $mHash, @a ) = @_; - my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; - my $name = $hash->{NAME}; - my $autoGoneAfter = AttrVal( $hash->{NAME}, "rr_autoGoneAfter", 36 ); - delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); - - RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); - - return if ( IsDisabled($name) || !$autoGoneAfter ); - - if ( ReadingsVal( $name, "state", "home" ) eq "absent" ) { - my ( $date, $time, $y, $m, $d, $hour, $min, $sec, $timestamp, - $timeDiff ); - my $timestampNow = gettimeofday(); - - ( $date, $time ) = split( ' ', $hash->{READINGS}{state}{TIME} ); - ( $y, $m, $d ) = split( '-', $date ); - ( $hour, $min, $sec ) = split( ':', $time ); - $m -= 01; - $timestamp = timelocal( $sec, $min, $hour, $d, $m, $y ); - $timeDiff = $timestampNow - $timestamp; - - if ( $timeDiff >= $autoGoneAfter * 3600 ) { - Log3 $name, 3, - "ROOMMATE $name: AutoGone timer changed state to 'gone'"; - ROOMMATE_Set( $hash, $name, "silentSet", "state", "gone" ); - } - else { - my $runtime = $timestamp + $autoGoneAfter * 3600; - $hash->{AUTOGONE} = $runtime; - Log3 $name, 4, "ROOMMATE $name: AutoGone timer scheduled: $runtime"; - RESIDENTStk_InternalTimer( "AutoGone", $runtime, - "ROOMMATE_AutoGone", $hash, 1 ); - } - } - - return undef; -} - -################################### -sub ROOMMATE_DurationTimer($;$) { - my ( $mHash, @a ) = @_; - my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; - my $name = $hash->{NAME}; - my $silent = ( defined( $a[0] ) && $a[0] eq "1" ) ? 1 : 0; - my $timestampNow = gettimeofday(); - my $diff; - my $durPresence = "0"; - my $durAbsence = "0"; - my $durSleep = "0"; - my $noDuration = AttrVal( $name, "rr_noDuration", 0 ); - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); - - return if ( IsDisabled($name) || $noDuration ); - - # presence timer - if ( ReadingsVal( $name, "presence", "absent" ) eq "present" - && ReadingsVal( $name, "lastArrival", "-" ) ne "-" ) - { - $durPresence = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastArrival", "" ) ); - } - - # absence timer - if ( ReadingsVal( $name, "presence", "present" ) eq "absent" - && ReadingsVal( $name, "lastDeparture", "-" ) ne "-" ) - { - $durAbsence = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastDeparture", "" ) ); - } - - # sleep timer - if ( ReadingsVal( $name, "state", "home" ) eq "asleep" - && ReadingsVal( $name, "lastSleep", "-" ) ne "-" ) - { - $durSleep = - $timestampNow - time_str2num( ReadingsVal( $name, "lastSleep", "" ) ); - } - - my $durPresence_hr = - ( $durPresence > 0 ) - ? RESIDENTStk_sec2time($durPresence) - : "00:00:00"; - my $durPresence_cr = - ( $durPresence > 60 ) ? int( $durPresence / 60 + 0.5 ) : 0; - my $durAbsence_hr = - ( $durAbsence > 0 ) ? RESIDENTStk_sec2time($durAbsence) : "00:00:00"; - my $durAbsence_cr = - ( $durAbsence > 60 ) ? int( $durAbsence / 60 + 0.5 ) : 0; - my $durSleep_hr = - ( $durSleep > 0 ) ? RESIDENTStk_sec2time($durSleep) : "00:00:00"; - my $durSleep_cr = ( $durSleep > 60 ) ? int( $durSleep / 60 + 0.5 ) : 0; - - readingsBeginUpdate($hash) if ( !$silent ); - readingsBulkUpdateIfChanged( $hash, "durTimerPresence_cr", - $durPresence_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerPresence", $durPresence_hr ); - readingsBulkUpdateIfChanged( $hash, "durTimerAbsence_cr", $durAbsence_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerAbsence", $durAbsence_hr ); - readingsBulkUpdateIfChanged( $hash, "durTimerSleep_cr", $durSleep_cr ); - readingsBulkUpdateIfChanged( $hash, "durTimerSleep", $durSleep_hr ); - readingsEndUpdate( $hash, 1 ) if ( !$silent ); - - $hash->{DURATIONTIMER} = $timestampNow + 60; - - RESIDENTStk_InternalTimer( "DurationTimer", $hash->{DURATIONTIMER}, - "ROOMMATE_DurationTimer", $hash, 1 ); - - return undef; -} - -################################### +# module Fn #################################################################### sub ROOMMATE_SetLocation($$$;$$$$$$) { my ( $name, $location, $trigger, $id, $time, $lat, $long, $address, $device ) = @_; @@ -1254,25 +885,6 @@ sub ROOMMATE_SetLocation($$$;$$$$$$) { } -################################### -sub ROOMMATE_StartInternalTimers($$) { - my ($hash) = @_; - - ROOMMATE_AutoGone($hash); - ROOMMATE_DurationTimer($hash); -} - -################################### -sub ROOMMATE_StopInternalTimers($) { - my ($hash) = @_; - - delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); - delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); - - RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); -} - 1; =pod diff --git a/fhem/FHEM/RESIDENTStk.pm b/fhem/FHEM/RESIDENTStk.pm index f91415fd1..aea2b8e94 100644 --- a/fhem/FHEM/RESIDENTStk.pm +++ b/fhem/FHEM/RESIDENTStk.pm @@ -1,31 +1,10 @@ +############################################################################### # $Id$ -############################################################################## -# -# RESIDENTStk.pm -# Additional functions for 10_RESIDENTS.pm, 20_ROOMMATE.pm, 20_GUEST.pm -# -# Copyright by Julian Pawlowski -# e-mail: julian.pawlowski at gmail.com -# -# This file is part of fhem. -# -# Fhem 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. -# -# Fhem 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. -# -# You should have received a copy of the GNU General Public License -# along with fhem. If not, see . -# -############################################################################## - -sub RESIDENTStk_Initialize() { -} +# package main; +# use strict; +# use warnings; +# use Data::Dumper; +sub RESIDENTStk_Initialize() { } ##################################### # PRE-DEFINITION: wakeuptimer @@ -38,22 +17,20 @@ sub RESIDENTStk_Initialize() { sub RESIDENTStk_wakeupSet($$) { my ( $NAME, $n ) = @_; my ( $a, $h ) = parseParams($n); - my $cmd = shift @$a; - my $VALUE = join( " ", @$a ); - my $nextRun = ReadingsVal( $NAME, "nextRun", "07:00" ); + my $cmd = shift @$a; + my $VALUE = join( " ", @$a ); + + $cmd =~ s/^state:\s*(.*)$/$1/; + return if ( $cmd =~ /^[A-Za-z]+:/ ); # filter non-registered notifies if ( $cmd !~ -m/^((?:next[rR]un)?\s*(off|OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?|trigger|start|stop|end|reset|auto|wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime)$/i +m/^((?:next[rR]un)?\s*(off|OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?|trigger|start|stop|end|reset|auto|wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime|wakeupOffset)$/i ) { Log3 $NAME, 6, - "RESIDENTStk $NAME: " - . "received unspecified notify '" - . $cmd - . "' - nothing to do"; - - fhem "set $NAME nextRun $nextRun"; + "RESIDENTStk $NAME: " + . "received unspecified notify '$cmd' - nothing to do"; return; } @@ -71,7 +48,8 @@ m/^((?:next[rR]un)?\s*(off|OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))? my $wakeupResetdays = ReadingsVal( $NAME, "wakeupResetdays", AttrVal( $NAME, "wakeupResetdays", "" ) ); - my $wakeupOffset = AttrVal( $NAME, "wakeupOffset", 0 ); + my $wakeupOffset = + ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) ); my $wakeupEnforced = ReadingsVal( $NAME, "wakeupEnforced", AttrVal( $NAME, "wakeupEnforced", 0 ) ); @@ -80,6 +58,7 @@ m/^((?:next[rR]un)?\s*(off|OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))? my $room = AttrVal( $NAME, "room", 0 ); my $userattr = AttrVal( $NAME, "userattr", 0 ); my $lastRun = ReadingsVal( $NAME, "lastRun", "07:00" ); + my $nextRun = ReadingsVal( $NAME, "nextRun", "07:00" ); my $running = ReadingsVal( $NAME, "running", 0 ); my $wakeupUserdeviceState = ReadingsVal( $wakeupUserdevice, "state", 0 ); my $atName = "at_" . $NAME; @@ -114,6 +93,8 @@ m/^((?:next[rR]un)?\s*(off|OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))? ); } + RESIDENTStk_findDummySlaves($wakeupUserdevice); + # check for required userattr attribute my $userattributes = "wakeupOffset:slider,0,1,120 wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupMacro wakeupUserdevice wakeupAtdevice wakeupResetSwitcher wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3 wakeupWaitPeriod:slider,0,1,360"; @@ -788,15 +769,32 @@ return;;\ } else { + # conditional enforced wake-up: + # only if actual wake-up time is + # earlier than wakeupDefaultTime + if ( $wakeupEnforced == 3 + && $wakeupDefaultTime + && RESIDENTStk_time2sec($wakeupDefaultTime) > + RESIDENTStk_time2sec($lastRun) ) + { + Log3 $NAME, 4, + "RESIDENTStk $NAME: " + . "Enforcing wake-up because wake-up time is earlier than normal (wakeupDefaultTime=$wakeupDefaultTime > lastRun=$lastRun)"; + $wakeupEnforced = 1; + } + # conditional enforced wake-up: # only if actual wake-up time is not wakeupDefaultTime - if ( $wakeupEnforced == 2 + elsif ($wakeupEnforced == 2 && $wakeupDefaultTime && $wakeupDefaultTime ne $lastRun ) { + Log3 $NAME, 4, + "RESIDENTStk $NAME: " + . "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)"; $wakeupEnforced = 1; } - elsif ( $wakeupEnforced == 2 ) { + elsif ( $wakeupEnforced > 1 ) { $wakeupEnforced = 0; } @@ -852,7 +850,7 @@ return;;\ # wakeup attributes # elsif ( $cmd =~ -m/^(wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime)$/ +m/^(wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime|wakeupOffset)$/ ) { Log3 $NAME, 4, "RESIDENTStk $NAME: " . "setting $1 to '$VALUE'"; @@ -915,7 +913,6 @@ m/^(?:nextRun)?\s*(OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?$/i readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice}, "nextWakeup", $nextWakeup ); readingsEndUpdate( $defs{$wakeupUserdevice}, 1 ); - } return undef; @@ -926,11 +923,20 @@ m/^(?:nextRun)?\s*(OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?$/i # sub RESIDENTStk_wakeupGetBegin($;$) { my ( $NAME, $wakeupAtdevice ) = @_; + + unless ( IsDevice($NAME) ) { + Log3 $NAME, 3, + "RESIDENTStk $NAME: " + . "Run function RESIDENTStk_wakeupGetBegin() for non-existing device!"; + return "$NAME: Non-existing device"; + } + my $nextRun = ReadingsVal( $NAME, "nextRun", 0 ); my $wakeupDefaultTime = ReadingsVal( $NAME, "wakeupDefaultTime", AttrVal( $NAME, "wakeupDefaultTime", 0 ) ); - my $wakeupOffset = AttrVal( $NAME, "wakeupOffset", 0 ); + my $wakeupOffset = + ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) ); my $wakeupInitTime = ( $wakeupDefaultTime && lc($wakeupDefaultTime) ne "off" ? $wakeupDefaultTime @@ -1013,6 +1019,13 @@ sub RESIDENTStk_wakeupGetBegin($;$) { sub RESIDENTStk_wakeupRun($;$) { my ( $NAME, $forceRun ) = @_; + unless ( IsDevice($NAME) ) { + Log3 $NAME, 3, + "RESIDENTStk $NAME: " + . "Run function RESIDENTStk_wakeupRun() for non-existing device!"; + return "$NAME: Non-existing device"; + } + my $wakeupMacro = AttrVal( $NAME, "wakeupMacro", 0 ); my $wakeupDefaultTime = ReadingsVal( $NAME, "wakeupDefaultTime", @@ -1027,7 +1040,8 @@ sub RESIDENTStk_wakeupRun($;$) { my $wakeupResetdays = ReadingsVal( $NAME, "wakeupResetdays", AttrVal( $NAME, "wakeupResetdays", "" ) ); - my $wakeupOffset = AttrVal( $NAME, "wakeupOffset", 0 ); + my $wakeupOffset = + ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) ); my $wakeupEnforced = ReadingsVal( $NAME, "wakeupEnforced", AttrVal( $NAME, "wakeupEnforced", 0 ) ); @@ -1092,18 +1106,10 @@ sub RESIDENTStk_wakeupRun($;$) { if ( $wakeupResetdays ne "" ); my %rdays = map { $_ => 1 } @rdays; - if ( !IsDevice($NAME) ) { - return "$NAME: Non existing device"; - } - elsif ( IsDisabled($wakeupDevice) ) { + if ( IsDisabled($wakeupDevice) ) { Log3 $name, 4, "RESIDENTStk $NAME: " - . "device disabled - not triggering wake-up program"; - } - elsif ( lc($nextRun) eq "off" && !$forceRun ) { - Log3 $NAME, 4, - "RESIDENTStk $NAME: " - . "alarm set to OFF - not triggering wake-up program"; + . "wakeupDevice disabled - not triggering wake-up program"; } elsif ( !$wakeupUserdevice ) { return "$NAME: missing attribute wakeupUserdevice"; @@ -1113,7 +1119,7 @@ sub RESIDENTStk_wakeupRun($;$) { } elsif ( !IsDevice( $wakeupUserdevice, "RESIDENTS|ROOMMATE|GUEST" ) ) { return "$NAME: " - . "device $wakeupUserdevice is not of type RESIDENTS, ROOMMATE or GUEST"; + . "wakeupUserdevice $wakeupUserdevice is not of type RESIDENTS, ROOMMATE or GUEST"; } elsif ( IsDevice( $wakeupUserdevice, "GUEST" ) && $wakeupUserdeviceState eq "none" ) @@ -1124,9 +1130,23 @@ sub RESIDENTStk_wakeupRun($;$) { fhem "set $NAME nextRun OFF"; return; } - elsif ($wakeupHolidays eq "" + elsif ( IsDisabled($wakeupUserdevice) ) { + Log3 $name, 4, + "RESIDENTStk $NAME: " + . "wakeupUserdevice disabled - not triggering wake-up program"; + } + elsif ( lc($nextRun) eq "off" && !$forceRun ) { + Log3 $NAME, 4, + "RESIDENTStk $NAME: " + . "wakeup timer set to OFF - not triggering wake-up program"; + } + elsif ( + !$forceRun && !$days{$today} - && !$forceRun ) + && ( $wakeupHolidays eq "" + || $wakeupHolidays eq "andHoliday" + || $wakeupHolidays eq "andNoHoliday" ) + ) { Log3 $NAME, 4, "RESIDENTStk $NAME: " @@ -1134,39 +1154,19 @@ sub RESIDENTStk_wakeupRun($;$) { } elsif ( !$forceRun - && ( $wakeupHolidays eq "orHoliday" - || $wakeupHolidays eq "orNoHoliday" ) && ( - !$days{$today} - && ( - ( $wakeupHolidays eq "orHoliday" && !$holidayToday ) - || ( $wakeupHolidays eq "orNoHoliday" - && $holidayToday ) - ) + ( $wakeupHolidays eq "andHoliday" && !$holidayToday ) + || ( $wakeupHolidays eq "andNoHoliday" + && $holidayToday ) + || ( $wakeupHolidays eq "orHoliday" && !$holidayToday ) + || ( $wakeupHolidays eq "orNoHoliday" + && $holidayToday ) ) ) { Log3 $NAME, 4, "RESIDENTStk $NAME: " - . "neither weekday nor holiday restriction matched - not triggering wake-up program this time"; - } - elsif ( - !$forceRun - && ( $wakeupHolidays eq "andHoliday" - || $wakeupHolidays eq "andNoHoliday" ) - && ( - !$days{$today} - || ( - ( $wakeupHolidays eq "andHoliday" && !$holidayToday ) - || ( $wakeupHolidays eq "andNoHoliday" - && $holidayToday ) - ) - ) - ) - { - Log3 $NAME, 4, - "RESIDENTStk $NAME: " - . "weekday restriction in conjunction with $wakeupHolidays in use - not triggering wake-up program this time"; + . "holiday restriction $wakeupHolidays in use - not triggering wake-up program this time"; } elsif ($wakeupUserdeviceState eq "absent" || $wakeupUserdeviceState eq "gone" @@ -1224,6 +1224,7 @@ sub RESIDENTStk_wakeupRun($;$) { . "won't trigger wake-up program due to non-expired wakeupWaitPeriod threshold since lastAwake (expLastAwake=$expLastAwake > nowRunSec=$nowRunSec)"; } else { + # conditional enforced wake-up: # only if actual wake-up time is # earlier than wakeupDefaultTime @@ -1249,7 +1250,7 @@ sub RESIDENTStk_wakeupRun($;$) { . "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)"; $wakeupEnforced = 1; } - elsif ( $wakeupEnforced == 2 ) { + elsif ( $wakeupEnforced > 1 ) { $wakeupEnforced = 0; } @@ -1289,7 +1290,6 @@ sub RESIDENTStk_wakeupRun($;$) { $running = 1; } - } } @@ -1348,11 +1348,9 @@ sub RESIDENTStk_wakeupRun($;$) { sub RESIDENTStk_AttrFnDummy(@) { my ( $cmd, $name, $aName, $aVal ) = @_; - # set attribute - if ( $init_done && $cmd eq "set" ) { - - # wakeupResetSwitcher - if ( $aName eq "wakeupResetSwitcher" ) { + # wakeupResetSwitcher + if ( $aName eq "wakeupResetSwitcher" ) { + if ( $init_done && $cmd eq "set" ) { if ( !IsDevice($aVal) ) { my $alias = AttrVal( $name, "alias", 0 ); my $group = AttrVal( $name, "group", 0 ); @@ -1360,7 +1358,7 @@ sub RESIDENTStk_AttrFnDummy(@) { fhem "define $aVal dummy"; fhem "attr $aVal " - . "comment Auto-created by RESIDENTS Toolkit: easy between on/off for auto time reset of wake-up timer $NAME"; + . "comment Auto-created by RESIDENTS Toolkit: easy switch between on/off for auto time reset of wake-up timer $NAME"; if ($alias) { fhem "attr $aVal alias $alias Reset"; } @@ -1381,14 +1379,12 @@ sub RESIDENTStk_AttrFnDummy(@) { Log3 $name, 3, "RESIDENTStk $name: new slave dummy device $aVal created"; } - elsif ( !IsDevice( $aVal, "dummy" ) ) { - Log3 $name, 3, - "RESIDENTStk $name: " - . "Defined device name in attr $aName is not a dummy device"; - return "Existing device $aVal is not a dummy!"; - } } + my $wakeupUserdevice = AttrVal( $name, "wakeupUserdevice", undef ); + if ( IsDevice( $wakeupUserdevice, "ROOMMATE|GUEST" ) ) { + RESIDENTStk_findDummySlaves($wakeupUserdevice); + } } return undef; @@ -1423,18 +1419,13 @@ sub RESIDENTStk_wakeupGetNext($;$) { my $secNow = RESIDENTStk_time2sec( $hour . ":" . $min ) + $sec; my $definitiveNextToday; my $definitiveNextTomorrow; - my $definitiveNextTodayDev = 0; - my $definitiveNextTomorrowDev = 0; + my $definitiveNextTodayDev; + my $definitiveNextTomorrowDev; my $holidayDevice = AttrVal( "global", "holiday2we", 0 ); # check for each registered wake-up device for my $wakeupDevice ( split /,/, $wakeupDeviceList ) { - next if !$wakeupDevice; - - my $ltoday = $today; - my $ltomorrow = $tomorrow; - if ( !IsDevice($wakeupDevice) ) { Log3 $name, 4, "RESIDENTStk $name: " @@ -1454,25 +1445,16 @@ sub RESIDENTStk_wakeupGetNext($;$) { next; } - elsif ( IsDisabled($wakeupDevice) ) { - Log3 $name, 4, - "RESIDENTStk $name: " - . "00 - ignoring disabled wakeupDevice $wakeupDevice"; - next; - } - Log3 $name, 4, - "RESIDENTStk $name: " - . "00 - checking for next wake-up candidate $wakeupDevice"; - - my $nextRun = ReadingsVal( $wakeupDevice, "nextRun", 0 ); - my $wakeupAtdevice = AttrVal( $wakeupDevice, "wakeupAtdevice", 0 ); - my $wakeupOffset = AttrVal( $wakeupDevice, "wakeupOffset", 0 ); - my $wakeupAtNTM = ( + my $wakeupAtdevice = AttrVal( $wakeupDevice, "wakeupAtdevice", undef ); + my $wakeupOffset = + ReadingsVal( $wakeupDevice, "wakeupOffset", + AttrVal( $wakeupDevice, "wakeupOffset", 0 ) ); + my $wakeupAtNTM = ( IsDevice($wakeupAtdevice) && defined( $defs{$wakeupAtdevice}{NTM} ) ? substr( $defs{$wakeupAtdevice}{NTM}, 0, -3 ) - : 0 + : undef ); my $wakeupDays = ReadingsVal( $wakeupDevice, "wakeupDays", @@ -1483,11 +1465,31 @@ sub RESIDENTStk_wakeupGetNext($;$) { my $holidayToday = 0; my $holidayTomorrow = 0; my $nextRunSrc; + my $nextRun = ReadingsVal( $wakeupDevice, "nextRun", undef ); + my $ltoday = $today; + my $ltomorrow = $tomorrow; + + if ( IsDisabled($wakeupDevice) + || !$nextRun + || lc($nextRun) eq "off" + || $nextRun !~ /^([0-9]{2}:[0-9]{2})$/ ) + { + Log3 $name, 4, + "RESIDENTStk $name: " + . "00 - ignoring disabled wakeupDevice $wakeupDevice"; + next; + } + + Log3 $name, 4, + "RESIDENTStk $name: " + . "00 - checking for next wake-up candidate $wakeupDevice"; # get holiday status for today and tomorrow - if ( $wakeupHolidays ne "" - && IsDevice( $holidayDevice, "holiday" ) ) - { + if ( $wakeupHolidays eq "" ) { + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: 01 - Not considering any holidays"; + } + elsif ( IsDevice( $holidayDevice, "holiday" ) ) { $holidayToday = 1 unless ( ReadingsVal( $holidayDevice, "state", "none" ) eq "none" ); @@ -1497,11 +1499,7 @@ sub RESIDENTStk_wakeupGetNext($;$) { Log3 $name, 4, "RESIDENTStk $wakeupDevice: " - . "01 - Holidays to be considered - today=$holidayToday tomorrow=$holidayTomorrow"; - } - else { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: 01 - Not considering any holidays"; + . "01 - Holidays to be considered ($wakeupHolidays) - holidayToday=$holidayToday holidayTomorrow=$holidayTomorrow"; } # set day scope for today @@ -1516,117 +1514,78 @@ sub RESIDENTStk_wakeupGetNext($;$) { if ( $wakeupDays ne "" ); my %daysTomorrow = map { $_ => 1 } @daysTomorrow; - if ( lc($nextRun) eq "off" - || $nextRun !~ /^([0-9]{2}:[0-9]{2})$/ ) + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "02 - possible candidate found - weekdayToday=$ltoday weekdayTomorrow=$ltomorrow"; + + my $nextRunSec; + my $nextRunSecTarget; + + # Use direct information from at-device if possible + if ( $wakeupAtNTM + && $wakeupAtNTM =~ /^([0-9]{2}:[0-9]{2})$/ ) { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " . "02 - set to OFF so no candidate"; - next; - } - else { + $nextRunSrc = "at"; + $nextRunSec = RESIDENTStk_time2sec($wakeupAtNTM); + $nextRunSecTarget = $nextRunSec + $wakeupOffset * 60; + + if ( $wakeupOffset && $nextRunSecTarget >= 86400 ) { + $nextRunSecTarget -= 86400; + + $ltoday++; + $ltoday = $ltoday - 7 + if ( $ltoday > 6 ); + + $ltomorrow++; + $ltomorrow = $ltomorrow - 7 + if ( $ltomorrow > 6 ); + } Log3 $name, 4, "RESIDENTStk $wakeupDevice: " - . "02 - possible candidate found - weekdayToday=$ltoday weekdayTomorrow=$ltomorrow"; + . "03 - considering at-device value wakeupAtNTM=$wakeupAtNTM wakeupOffset=$wakeupOffset nextRunSec=$nextRunSec nextRunSecTarget=$nextRunSecTarget"; + } + else { + $nextRunSrc = "dummy"; + $nextRunSecTarget = RESIDENTStk_time2sec($nextRun); + $nextRunSec = $nextRunSecTarget - $wakeupOffset * 60; - my $nextRunSec; - my $nextRunSecTarget; + if ( $wakeupOffset && $nextRunSec < 0 ) { + $nextRunSec += 86400; - # Use direct information from at-device if possible - if ( $wakeupAtNTM - && $wakeupAtNTM =~ /^([0-9]{2}:[0-9]{2})$/ ) - { - $nextRunSrc = "at"; - $nextRunSec = RESIDENTStk_time2sec($wakeupAtNTM); - $nextRunSecTarget = $nextRunSec + $wakeupOffset * 60; + $ltoday--; + $ltoday = $ltoday + 7 + if ( $ltoday < 0 ); - if ( $wakeupOffset && $nextRunSecTarget >= 86400 ) { - $nextRunSecTarget -= 86400; - - $ltoday++; - $ltoday = $ltoday - 7 - if ( $ltoday > 6 ); - - $ltomorrow++; - $ltomorrow = $ltomorrow - 7 - if ( $ltomorrow > 6 ); - } - - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "03 - considering at-device value wakeupAtNTM=$wakeupAtNTM wakeupOffset=$wakeupOffset nextRunSec=$nextRunSec nextRunSecTarget=$nextRunSecTarget"; - } - else { - $nextRunSrc = "dummy"; - $nextRunSecTarget = RESIDENTStk_time2sec($nextRun); - $nextRunSec = $nextRunSecTarget - $wakeupOffset * 60; - - if ( $wakeupOffset && $nextRunSec < 0 ) { - $nextRunSec += 86400; - - $ltoday--; - $ltoday = $ltoday + 7 - if ( $ltoday < 0 ); - - $ltomorrow--; - $ltomorrow = $ltomorrow + 7 - if ( $ltomorrow < 0 ); - } - - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "03 - considering dummy-device value nextRun=$nextRun wakeupOffset=$wakeupOffset nextRunSec=$nextRunSec nextRunSecTarget=$nextRunSecTarget (wakeupAtNTM=$wakeupAtNTM)"; + $ltomorrow--; + $ltomorrow = $ltomorrow + 7 + if ( $ltomorrow < 0 ); } - # still running today - if ( $nextRunSec > $secNow ) { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "04 - this is a candidate for today - weekdayToday=$ltoday"; + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "03 - considering dummy-device value nextRun=$nextRun wakeupOffset=$wakeupOffset nextRunSec=$nextRunSec nextRunSecTarget=$nextRunSecTarget (wakeupAtNTM=$wakeupAtNTM)"; + } - # if today is in scope - if ( $days{$ltoday} ) { + # still running today + if ( $nextRunSec > $secNow ) { + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "04 - this is a candidate for today - weekdayToday=$ltoday"; - # if we need to consider holidays in addition - if ( - ( $wakeupHolidays eq "andHoliday" && !$holidayToday ) - || ( $wakeupHolidays eq "andNoHoliday" - && $holidayToday ) - ) - { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "05 - no run today due to holiday based on combined weekday and holiday decision"; - next; - } + # if today is in scope + if ( $days{$ltoday} ) { - # easy if there is no holiday dependency - elsif ( !$definitiveNextToday - || $nextRunSec < $definitiveNextToday ) - { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "05 - until now, will be NEXT WAKE-UP RUN today based on weekday decision"; - $definitiveNextToday = $nextRunSec; - $definitiveNextTodayDev = $wakeupDevice; - } - - } - else { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "05 - won't be running today anymore based on weekday decision"; - next; - } - - # if we need to consider holidays in parallel to weekdays - if ( ( $wakeupHolidays eq "orHoliday" && $holidayToday ) - || ( $wakeupHolidays eq "orNoHoliday" && !$holidayToday ) ) + # if we need to consider holidays in addition + if ( + ( $wakeupHolidays eq "andHoliday" && !$holidayToday ) + || ( $wakeupHolidays eq "andNoHoliday" + && $holidayToday ) + ) { - Log3 $name, 4, "RESIDENTStk $wakeupDevice: " - . "06 - won't be running today based on holiday decision"; + . "05 - no run today due to holiday based on combined weekday and holiday decision"; next; } @@ -1636,82 +1595,111 @@ sub RESIDENTStk_wakeupGetNext($;$) { { Log3 $name, 4, "RESIDENTStk $wakeupDevice: " - . "06 - until now, will be NEXT WAKE-UP RUN today based on holiday decision"; + . "05 - until now, will be NEXT WAKE-UP RUN today based on weekday decision"; $definitiveNextToday = $nextRunSec; $definitiveNextTodayDev = $wakeupDevice; } - } - # running later - else { + elsif ($wakeupHolidays eq "" + || $wakeupHolidays eq "andHoliday" + || $wakeupHolidays eq "andNoHoliday" ) + { Log3 $name, 4, "RESIDENTStk $wakeupDevice: " - . "04 - this is a candidate for tomorrow or later - weekdayTomorrow=$ltomorrow"; + . "05 - won't be running today anymore based on weekday decision"; + next; + } - # if tomorrow is in scope - if ( $daysTomorrow{$ltomorrow} ) { + # if we need to consider holidays in parallel to weekdays + elsif (( $wakeupHolidays eq "orHoliday" && !$holidayToday ) + || ( $wakeupHolidays eq "orNoHoliday" && $holidayToday ) ) + { + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "06 - won't be running today based on holiday decision"; + next; + } - # if we need to consider holidays in addition - if ( - ( - $wakeupHolidays eq "andHoliday" - && !$holidayTomorrow - ) - || ( $wakeupHolidays eq "andNoHoliday" - && $holidayTomorrow ) - ) - { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "05 - no run tomorrow due to holiday based on combined weekday and holiday decision"; - next; - } + # easy if there is no holiday dependency + elsif ( !$definitiveNextToday + || $nextRunSec < $definitiveNextToday ) + { + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "06 - until now, will be NEXT WAKE-UP RUN today based on holiday decision"; + $definitiveNextToday = $nextRunSec; + $definitiveNextTodayDev = $wakeupDevice; + } + } - # easy if there is no holiday dependency - elsif ( !$definitiveNextTomorrow - || $nextRunSec < $definitiveNextTomorrow ) - { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "05 - until now, will be NEXT WAKE-UP RUN tomorrow based on weekday decision"; - $definitiveNextTomorrow = $nextRunSec; - $definitiveNextTomorrowDev = $wakeupDevice; - } + # running later + else { + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "04 - this is a candidate for tomorrow or later - weekdayTomorrow=$ltomorrow"; - } - else { - Log3 $name, 4, - "RESIDENTStk $wakeupDevice: " - . "05 - won't be running tomorrow based on weekday decision"; - next; - } + # if tomorrow is in scope + if ( $daysTomorrow{$ltomorrow} ) { - # if we need to consider holidays in parallel to weekdays + # if we need to consider holidays in addition if ( - ( $wakeupHolidays eq "orHoliday" && $holidayTomorrow ) - || ( $wakeupHolidays eq "orNoHoliday" - && !$holidayTomorrow ) + ( $wakeupHolidays eq "andHoliday" && !$holidayTomorrow ) + || ( $wakeupHolidays eq "andNoHoliday" + && $holidayTomorrow ) ) { Log3 $name, 4, "RESIDENTStk $wakeupDevice: " - . "06 - won't be running tomorrow based on holiday decision"; + . "05 - no run tomorrow due to holiday based on combined weekday and holiday decision"; next; } + # easy if there is no holiday dependency elsif ( !$definitiveNextTomorrow || $nextRunSec < $definitiveNextTomorrow ) { Log3 $name, 4, "RESIDENTStk $wakeupDevice: " - . "06 - until now, will be NEXT WAKE-UP RUN tomorrow based on holiday decision"; + . "05 - until now, will be NEXT WAKE-UP RUN tomorrow based on weekday decision"; $definitiveNextTomorrow = $nextRunSec; $definitiveNextTomorrowDev = $wakeupDevice; } - } + elsif ($wakeupHolidays eq "" + || $wakeupHolidays eq "andHoliday" + || $wakeupHolidays eq "andNoHoliday" ) + { + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "05 - won't be running tomorrow based on weekday decision"; + next; + } + + # if we need to consider holidays in parallel to weekdays + elsif ( + ( $wakeupHolidays eq "orHoliday" && !$holidayTomorrow ) + || ( $wakeupHolidays eq "orNoHoliday" + && $holidayTomorrow ) + ) + { + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "06 - won't be running tomorrow based on holiday decision"; + next; + } + + # easy if there is no holiday dependency + elsif ( !$definitiveNextTomorrow + || $nextRunSec < $definitiveNextTomorrow ) + { + Log3 $name, 4, + "RESIDENTStk $wakeupDevice: " + . "06 - until now, will be NEXT WAKE-UP RUN tomorrow based on holiday decision"; + $definitiveNextTomorrow = $nextRunSec; + $definitiveNextTomorrowDev = $wakeupDevice; + } } if ($wakeupOffset) { @@ -1900,11 +1888,30 @@ sub RESIDENTStk_RemoveInternalTimer($$) { } } -sub RESIDENTStk_findResidentSlaves($) { +sub RESIDENTStk_RG_StartInternalTimers($$) { my ($hash) = @_; + + RESIDENTStk_RG_AutoGone($hash); + RESIDENTStk_RG_DurationTimer($hash); +} + +sub RESIDENTStk_RG_StopInternalTimers($) { + my ($hash) = @_; + + delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); + delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); + + RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); + RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); +} + +sub RESIDENTStk_findResidentSlaves($;$) { + my ( $hash, $rgr_wakeupDevice ) = @_; return unless ( ref($hash) eq "HASH" && defined( $hash->{NAME} ) ); + $hash->{NOTIFYDEV} = ""; + delete $hash->{ROOMMATES}; foreach ( devspec2array("TYPE=ROOMMATE") ) { next @@ -1928,6 +1935,386 @@ sub RESIDENTStk_findResidentSlaves($) { $hash->{GUESTS} .= "," if ( $hash->{GUESTS} ); $hash->{GUESTS} .= $_; } + + $hash->{NOTIFYDEV} = $hash->{ROOMMATES} if ( $hash->{ROOMMATES} ); + $hash->{NOTIFYDEV} .= "," if ( $hash->{NOTIFYDEV} ne "" ); + $hash->{NOTIFYDEV} .= $hash->{GUESTS} if ( $hash->{GUESTS} ); + + RESIDENTStk_findDummySlaves( $hash->{NAME} ); +} + +sub RESIDENTStk_findDummySlaves($;$$) { + my ( $name, $wakeupDevice, $presenceDevices ) = @_; + my $hash = $defs{$name}; + my $TYPE = GetType($name); + my $prefix = RESIDENTStk_GetPrefixFromType($name); + + $wakeupDevice = AttrVal( $name, $prefix . "wakeupDevice", undef ) + unless ( !$init_done || $wakeupDevice ); + $presenceDevices = AttrVal( $name, $prefix . "presenceDevices", undef ) + unless ( !$init_done || $presenceDevices ); + + $hash->{NOTIFYDEV} = "global" unless ( $prefix eq "rgr_" ); + + if ($wakeupDevice) { + $hash->{NOTIFYDEV} .= "," if ( $hash->{NOTIFYDEV} ne "" ); + $hash->{NOTIFYDEV} .= $wakeupDevice; + + my @wakeupdevs = + split( ',', $wakeupDevice ); + + foreach (@wakeupdevs) { + my $rsw; + next unless ( IsDevice($_) ); + + $rsw = AttrVal( $_, "wakeupResetSwitcher", "" ) if ($init_done); + + if ( $rsw =~ /^[a-zA-Z\d._]+$/ ) { + $hash->{NOTIFYDEV} .= "," if ( $hash->{NOTIFYDEV} ne "" ); + $hash->{NOTIFYDEV} .= $rsw; + } + } + } + + if ($presenceDevices) { + $hash->{NOTIFYDEV} .= "," if ( $hash->{NOTIFYDEV} ne "" ); + $hash->{NOTIFYDEV} .= $presenceDevices; + } +} + +sub RESIDENTStk_GetPrefixFromType($) { + my ($name) = @_; + return "rgr_" if ( GetType($name) eq "RESIDENTS" ); + return "rr_" if ( GetType($name) eq "ROOMMATE" ); + return "rg_" if ( GetType($name) eq "GUEST" ); + return ""; +} + +sub RESIDENTStk_RG_Attr(@) { + my ( $cmd, $name, $attribute, $value ) = @_; + my $hash = $defs{$name}; + my $prefix = RESIDENTStk_GetPrefixFromType($name); + + if ( $attribute eq $prefix . "wakeupDevice" + || $attribute eq $prefix . "presenceDevices" ) + { + return "Value for $attribute has invalid format" + unless ( $cmd eq "del" + || $value =~ /^([a-zA-Z\d._]+,?)([a-zA-Z\d._]+,?)*$/ ); + + $value = "" if ( $cmd eq "del" ); + + my $wakeupDevice = + $attribute eq $prefix . "wakeupDevice" + ? $value + : AttrVal( $name, $prefix . "wakeupDevice", undef ); + my $presenceDevices = + $attribute eq $prefix . "presenceDevices" + ? $value + : AttrVal( $name, $prefix . "presenceDevices", undef ); + + RESIDENTStk_findDummySlaves( $name, $wakeupDevice, $presenceDevices ); + } + + elsif ( !$init_done ) { + return undef; + } + + elsif ( $attribute eq "disable" ) { + if ( $value and $value == 1 ) { + $hash->{STATE} = "disabled"; + RESIDENTStk_RG_StopInternalTimers($hash); + } + elsif ( $cmd eq "del" or !$value ) { + evalStateFormat($hash); + RESIDENTStk_RG_StartInternalTimers( $hash, 1 ); + } + } + + elsif ( $attribute eq $prefix . "autoGoneAfter" ) { + if ($value) { + RESIDENTStk_RG_AutoGone($hash); + } + elsif ( !$value ) { + delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); + RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); + } + } + + elsif ( $attribute eq $prefix . "noDuration" ) { + if ($value) { + delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); + RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); + } + elsif ( !$value ) { + RESIDENTStk_RG_DurationTimer($hash); + } + } + + elsif ( $attribute eq $prefix . "lang" ) { + my $lang = + $cmd eq "set" ? uc($value) : AttrVal( "global", "language", "EN" ); + + # for initial define, ensure fallback to EN + $lang = "EN" + if ( $cmd eq "init" && $lang !~ /^EN|DE$/i ); + + if ( $lang eq "DE" ) { + $attr{$name}{devStateIcon} = +'.*zuhause:user_available:absent .*anwesend:user_available:absent .*abwesend:user_away:home .*verreist:user_ext_away:home .*bettfertig:scene_toilet:asleep .*schlaeft:scene_sleeping:awoken .*schläft:scene_sleeping:awoken .*aufgestanden:scene_sleeping_alternat:home .*:user_unknown:home'; + $attr{$name}{eventMap} = +"home:zuhause absent:abwesend gone:verreist gotosleep:bettfertig asleep:schläft awoken:aufgestanden"; + $attr{$name}{widgetOverride} = +"state:zuhause,bettfertig,schläft,aufgestanden,abwesend,verreist"; + } + elsif ( $lang eq "EN" ) { + $attr{$name}{devStateIcon} = +'.*home:user_available:absent .*absent:user_away:home .*gone:user_ext_away:home .*gotosleep:scene_toilet:asleep .*asleep:scene_sleeping:awoken .*awoken:scene_sleeping_alternat:home .*:user_unknown:home'; + delete $attr{$name}{eventMap} + if ( defined( $attr{$name}{eventMap} ) ); + delete $attr{$name}{widgetOverride} + if ( defined( $attr{$name}{widgetOverride} ) ); + } + else { + return "Unsupported language $lang"; + } + + evalStateFormat($hash); + } + + return undef; +} + +sub RESIDENTStk_RG_Notify($$) { + my ( $hash, $dev ) = @_; + my $name = $hash->{NAME}; + my $TYPE = GetType($name); + my $prefix = RESIDENTStk_GetPrefixFromType($name); + my $devName = $dev->{NAME}; + return "" if ( IsDisabled($name) or IsDisabled($devName) ); + + if ( $devName eq "global" ) { + my $events = deviceEvents( $dev, 0 ); + return "" + unless ( $events && grep( m/^INITIALIZED|REREADCFG$/, @{$events} ) ); + RESIDENTStk_findDummySlaves($name); + return ""; + } + + delete $dev->{CHANGEDWITHSTATE}; + my $events = deviceEvents( $dev, 1 ); + return "" unless ($events); + + # process wakeup devices + my @registeredWakeupdevs = + split( ',', AttrVal( $name, $prefix . "wakeupDevice", "" ) ); + if (@registeredWakeupdevs) { + + # if this is a notification of a registered wakeup device + if ( grep { m/^$devName$/ } @registeredWakeupdevs ) { + + foreach my $event ( @{$events} ) { + next unless ( defined($event) ); + RESIDENTStk_wakeupSet( $devName, $event ); + } + + return ""; + } + + # process sub-child notifies: *_wakeupDevice + foreach my $wakeupDev (@registeredWakeupdevs) { + + # if this is a notification of a registered sub dummy device + # of one of our wakeup devices + if ( AttrVal( $wakeupDev, "wakeupResetSwitcher", "" ) eq $devName + && IsDevice( $devName, "dummy" ) ) + { + foreach my $event ( @{$events} ) { + next unless ( defined($event) ); + RESIDENTStk_wakeupSet( $wakeupDev, $event ) + unless ( $event =~ /^(?:state:\s*)?off$/i ); + } + + return ""; + } + } + + return ""; + } + + # process PRESENCE + my @presenceDevices = + split( ',', AttrVal( $name, $prefix . "presenceDevices", "" ) ); + if ( @presenceDevices + && grep { /^[\s\t ]*$devName(:[A-Za-z\d_\.\-\/]*)?[\s\t ]*$/ } + @presenceDevices ) + { + + my $counter = { + absent => 0, + present => 0, + }; + + for (@presenceDevices) { + my $r = "presence"; + my $d = $_; + if ( $d =~ + m/^[\s\t ]*([A-Za-z\d_\.\-\/]+):([A-Za-z\d_\.\-\/]+)?[\s\t ]*$/ + ) + { + $d = $1; + $r = $2; + } + + my $presenceState = + ReadingsVal( $d, $r, ReadingsVal( $d, "state", "" ) ); + next + unless ( $presenceState =~ +m/^(0|false|absent|disappeared|unavailable|unreachable|disconnected)|(1|true|present|appeared|available|reachable|connected|)$/i + ); + + $counter->{absent}++ if ($1); + $counter->{present}++ if ($2); + } + + if ( $counter->{absent} && !$counter->{present} ) { + Log3 $name, 4, + "$TYPE $name: " . "Syncing status with $devName = absent"; + fhem "set $name:FILTER=presence=present absent"; + } + elsif ( $counter->{present} ) { + Log3 $name, 4, + "$TYPE $name: " . "Syncing status with $devName = present"; + fhem "set $name:FILTER=presence=absent home"; + } + + return ""; + } + + return ""; +} + +sub RESIDENTStk_RG_AutoGone($;$) { + my ( $mHash, @a ) = @_; + my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; + my $name = $hash->{NAME}; + my $TYPE = GetType($name); + my $prefix = RESIDENTStk_GetPrefixFromType($name); + my $autoGoneAfter = AttrVal( $hash->{NAME}, $prefix . "autoGoneAfter", 36 ); + delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); + + RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); + + return if ( IsDisabled($name) || !$autoGoneAfter ); + + if ( ReadingsVal( $name, "state", "home" ) eq "absent" ) { + my ( $date, $time, $y, $m, $d, $hour, $min, $sec, $timestamp, + $timeDiff ); + my $timestampNow = gettimeofday(); + + ( $date, $time ) = split( ' ', $hash->{READINGS}{state}{TIME} ); + ( $y, $m, $d ) = split( '-', $date ); + ( $hour, $min, $sec ) = split( ':', $time ); + $m -= 01; + $timestamp = timelocal( $sec, $min, $hour, $d, $m, $y ); + $timeDiff = $timestampNow - $timestamp; + + if ( $timeDiff >= $autoGoneAfter * 3600 ) { + Log3 $name, 3, + "$TYPE $name: AutoGone timer changed state to 'gone'"; + &{ $TYPE . "_Set" }( $hash, $name, "silentSet", "state", "gone" ); + } + else { + my $runtime = $timestamp + $autoGoneAfter * 3600; + $hash->{AUTOGONE} = $runtime; + Log3 $name, 4, "$TYPE $name: AutoGone timer scheduled: $runtime"; + RESIDENTStk_InternalTimer( "AutoGone", $runtime, + "RESIDENTStk_RG_AutoGone", $hash, 1 ); + } + } + + return undef; +} + +sub RESIDENTStk_RG_DurationTimer($;$) { + my ( $mHash, @a ) = @_; + my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; + my $name = $hash->{NAME}; + my $TYPE = GetType($name); + my $prefix = RESIDENTStk_GetPrefixFromType($name); + my $silent = ( defined( $a[0] ) && $a[0] eq "1" ) ? 1 : 0; + my $timestampNow = gettimeofday(); + my $diff; + my $durPresence = "0"; + my $durAbsence = "0"; + my $durSleep = "0"; + my $noDuration = AttrVal( $name, $prefix . "noDuration", 0 ); + delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); + + RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); + + return if ( IsDisabled($name) || $noDuration ); + + # presence timer + if ( ReadingsVal( $name, "presence", "absent" ) eq "present" + && ReadingsVal( $name, "lastArrival", "-" ) ne "-" ) + { + $durPresence = + $timestampNow - + time_str2num( ReadingsVal( $name, "lastArrival", "" ) ); + } + + # absence timer + if ( ReadingsVal( $name, "presence", "present" ) eq "absent" + && ReadingsVal( $name, "lastDeparture", "-" ) ne "-" ) + { + $durAbsence = + $timestampNow - + time_str2num( ReadingsVal( $name, "lastDeparture", "" ) ); + } + + # sleep timer + if ( ReadingsVal( $name, "state", "home" ) eq "asleep" + && ReadingsVal( $name, "lastSleep", "-" ) ne "-" ) + { + $durSleep = + $timestampNow - time_str2num( ReadingsVal( $name, "lastSleep", "" ) ); + } + + my $durPresence_hr = + ( $durPresence > 0 ) + ? RESIDENTStk_sec2time($durPresence) + : "00:00:00"; + my $durPresence_cr = + ( $durPresence > 60 ) ? int( $durPresence / 60 + 0.5 ) : 0; + my $durAbsence_hr = + ( $durAbsence > 0 ) ? RESIDENTStk_sec2time($durAbsence) : "00:00:00"; + my $durAbsence_cr = + ( $durAbsence > 60 ) ? int( $durAbsence / 60 + 0.5 ) : 0; + my $durSleep_hr = + ( $durSleep > 0 ) ? RESIDENTStk_sec2time($durSleep) : "00:00:00"; + my $durSleep_cr = ( $durSleep > 60 ) ? int( $durSleep / 60 + 0.5 ) : 0; + + readingsBeginUpdate($hash) if ( !$silent ); + readingsBulkUpdateIfChanged( $hash, "durTimerPresence_cr", + $durPresence_cr ); + readingsBulkUpdateIfChanged( $hash, "durTimerPresence", $durPresence_hr ); + readingsBulkUpdateIfChanged( $hash, "durTimerAbsence_cr", $durAbsence_cr ); + readingsBulkUpdateIfChanged( $hash, "durTimerAbsence", $durAbsence_hr ); + readingsBulkUpdateIfChanged( $hash, "durTimerSleep_cr", $durSleep_cr ); + readingsBulkUpdateIfChanged( $hash, "durTimerSleep", $durSleep_hr ); + readingsEndUpdate( $hash, 1 ) if ( !$silent ); + + $hash->{DURATIONTIMER} = $timestampNow + 60; + + RESIDENTStk_InternalTimer( + "DurationTimer", + $hash->{DURATIONTIMER}, + "RESIDENTStk_RG_DurationTimer", + $hash, 1 + ); + + return undef; } 1;