2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-04 05:16:45 +00:00

RESIDENTS,ROOMMATE,GUEST: refactoring notification system and improved wakeuptimer

git-svn-id: https://svn.fhem.de/fhem/trunk@14011 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
jpawlowski 2017-04-17 12:04:50 +00:00
parent 1f52e62465
commit 591a2717a1
4 changed files with 897 additions and 1429 deletions

View File

@ -1,60 +1,29 @@
###############################################################################
# $Id$ # $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 <http://www.gnu.org/licenses/>.
#
##############################################################################
package main; package main;
use strict; use strict;
use warnings; use warnings;
use Time::Local;
use Data::Dumper; use Data::Dumper;
use Time::Local;
require RESIDENTStk; require RESIDENTStk;
sub RESIDENTS_Set($@); # initialize ##################################################################
sub RESIDENTS_Define($$);
sub RESIDENTS_Notify($$);
sub RESIDENTS_Attr(@);
sub RESIDENTS_Undefine($$);
###################################
sub RESIDENTS_Initialize($) { sub RESIDENTS_Initialize($) {
my ($hash) = @_; my ($hash) = @_;
Log3 $hash, 5, "RESIDENTS_Initialize: Entering";
$hash->{SetFn} = "RESIDENTS_Set";
$hash->{DefFn} = "RESIDENTS_Define"; $hash->{DefFn} = "RESIDENTS_Define";
$hash->{NotifyFn} = "RESIDENTS_Notify";
$hash->{AttrFn} = "RESIDENTS_Attr";
$hash->{UndefFn} = "RESIDENTS_Undefine"; $hash->{UndefFn} = "RESIDENTS_Undefine";
$hash->{SetFn} = "RESIDENTS_Set";
$hash->{AttrFn} = "RESIDENTS_Attr";
$hash->{NotifyFn} = "RESIDENTS_Notify";
$hash->{AttrList} = $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; . $readingFnAttributes;
} }
################################### # regular Fn ##################################################################
sub RESIDENTS_Define($$) { sub RESIDENTS_Define($$) {
my ( $hash, $def ) = @_; my ( $hash, $def ) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -75,7 +44,7 @@ sub RESIDENTS_Define($$) {
# run timers # run timers
InternalTimer( InternalTimer(
gettimeofday() + 15, gettimeofday() + 15,
"RESIDENTS_StartInternalTimers", "RESIDENTStk_RG_StartInternalTimers",
$hash, 0 $hash, 0
); );
@ -91,88 +60,10 @@ sub RESIDENTS_Define($$) {
return undef; 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($$) { sub RESIDENTS_Undefine($$) {
my ( $hash, $name ) = @_; my ( $hash, $name ) = @_;
RESIDENTS_StopInternalTimers($hash); RESIDENTStk_RG_StopInternalTimers($hash);
RESIDENTStk_findResidentSlaves($hash); RESIDENTStk_findResidentSlaves($hash);
# delete child roommates # delete child roommates
@ -204,132 +95,6 @@ sub RESIDENTS_Undefine($$) {
return undef; 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($@) { sub RESIDENTS_Set($@) {
my ( $hash, @a ) = @_; my ( $hash, @a ) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -570,8 +335,7 @@ sub RESIDENTS_Set($@) {
# create # create
elsif ( $a[1] eq "create" ) { elsif ( $a[1] eq "create" ) {
if ( !defined( $a[2] ) || $a[2] !~ /^(wakeuptimer)$/i ) { if ( !defined( $a[2] ) || $a[2] !~ /^(wakeuptimer)$/i ) {
return return "Invalid 2nd argument, choose one of wakeuptimer ";
"Invalid 2nd argument, choose one of wakeuptimer ";
} }
elsif ( $a[2] eq "wakeuptimer" ) { elsif ( $a[2] eq "wakeuptimer" ) {
my $i = "1"; my $i = "1";
@ -639,12 +403,190 @@ sub RESIDENTS_Set($@) {
return undef; return undef;
} }
############################################################################################################ sub RESIDENTS_Attr(@) {
# my ( $cmd, $name, $attribute, $value ) = @_;
# Begin of helper functions 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 (@) { sub RESIDENTS_UpdateReadings (@) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -1511,96 +1453,7 @@ sub RESIDENTS_UpdateReadings (@) {
} }
# calculate duration timers # calculate duration timers
RESIDENTS_DurationTimer( $hash, 1 ); RESIDENTStk_RG_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 );
} }
1; 1;

View File

@ -1,60 +1,29 @@
###############################################################################
# $Id$ # $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 <http://www.gnu.org/licenses/>.
#
##############################################################################
package main; package main;
use strict; use strict;
use warnings; use warnings;
use Time::Local;
use Data::Dumper; use Data::Dumper;
use Time::Local;
require RESIDENTStk; require RESIDENTStk;
sub GUEST_Set($@); # initialize ##################################################################
sub GUEST_Define($$);
sub GUEST_Notify($$);
sub GUEST_Attr(@);
sub GUEST_Undefine($$);
###################################
sub GUEST_Initialize($) { sub GUEST_Initialize($) {
my ($hash) = @_; my ($hash) = @_;
Log3 $hash, 5, "GUEST_Initialize: Entering";
$hash->{SetFn} = "GUEST_Set";
$hash->{DefFn} = "GUEST_Define"; $hash->{DefFn} = "GUEST_Define";
$hash->{NotifyFn} = "GUEST_Notify";
$hash->{AttrFn} = "GUEST_Attr";
$hash->{UndefFn} = "GUEST_Undefine"; $hash->{UndefFn} = "GUEST_Undefine";
$hash->{SetFn} = "GUEST_Set";
$hash->{AttrFn} = "RESIDENTStk_RG_Attr";
$hash->{NotifyFn} = "RESIDENTStk_RG_Notify";
$hash->{AttrList} = $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; . $readingFnAttributes;
} }
################################### # regular Fn ##################################################################
sub GUEST_Define($$) { sub GUEST_Define($$) {
my ( $hash, $def ) = @_; my ( $hash, $def ) = @_;
my @a = split( "[ \t][ \t]*", $def ); my @a = split( "[ \t][ \t]*", $def );
@ -69,6 +38,8 @@ sub GUEST_Define($$) {
return $msg; return $msg;
} }
$hash->{NOTIFYDEV} = "";
$hash->{RESIDENTGROUPS} = defined( $a[2] ) ? $a[2] : ""; $hash->{RESIDENTGROUPS} = defined( $a[2] ) ? $a[2] : "";
if ( defined( $hash->{RESIDENTGROUPS} ) ) { if ( defined( $hash->{RESIDENTGROUPS} ) ) {
foreach ( split( /,/, $hash->{RESIDENTGROUPS} ) ) { foreach ( split( /,/, $hash->{RESIDENTGROUPS} ) ) {
@ -111,7 +82,11 @@ sub GUEST_Define($$) {
readingsEndUpdate( $hash, 1 ); readingsEndUpdate( $hash, 1 );
# run timers # run timers
InternalTimer( gettimeofday() + 15, "GUEST_StartInternalTimers", $hash, 0 ); InternalTimer(
gettimeofday() + 15,
"RESIDENTStk_RG_StartInternalTimers",
$hash, 0
);
# Injecting AttrFn for use with RESIDENTS Toolkit # Injecting AttrFn for use with RESIDENTS Toolkit
if ( !defined( $modules{dummy}{AttrFn} ) ) { if ( !defined( $modules{dummy}{AttrFn} ) ) {
@ -127,86 +102,10 @@ sub GUEST_Define($$) {
return undef; 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($$) { sub GUEST_Undefine($$) {
my ( $hash, $name ) = @_; my ( $hash, $name ) = @_;
GUEST_StopInternalTimers($hash); RESIDENTStk_RG_StopInternalTimers($hash);
if ( defined( $hash->{RESIDENTGROUPS} ) ) { if ( defined( $hash->{RESIDENTGROUPS} ) ) {
my $old = $hash->{RESIDENTGROUPS}; my $old = $hash->{RESIDENTGROUPS};
@ -223,147 +122,8 @@ sub GUEST_Undefine($$) {
return undef; return undef;
} }
################################### sub GUEST_Set($@);
sub GUEST_Notify($$) {
my ( $hash, $dev ) = @_;
my $devName = $dev->{NAME};
my $hashName = $hash->{NAME};
return if ( IsDisabled($hashName) or IsDisabled($devName) );
# 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($@) { sub GUEST_Set($@) {
my ( $hash, @a ) = @_; my ( $hash, @a ) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -728,13 +488,13 @@ sub GUEST_Set($@) {
} }
# calculate duration timers # calculate duration timers
GUEST_DurationTimer( $hash, $silent ); RESIDENTStk_RG_DurationTimer( $hash, $silent );
readingsEndUpdate( $hash, 1 ); readingsEndUpdate( $hash, 1 );
# enable or disable AutoGone timer # enable or disable AutoGone timer
if ( $newstate eq "absent" ) { if ( $newstate eq "absent" ) {
GUEST_AutoGone($hash); RESIDENTStk_RG_AutoGone($hash);
} }
elsif ( $state eq "absent" ) { elsif ( $state eq "absent" ) {
delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} );
@ -888,7 +648,7 @@ sub GUEST_Set($@) {
fhem "attr $wakeuptimerName room " . $attr{$name}{room} fhem "attr $wakeuptimerName room " . $attr{$name}{room}
if ( defined( $attr{$name}{room} ) ); if ( defined( $attr{$name}{room} ) );
fhem 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 userattr wakeupUserdevice";
fhem "attr $wakeuptimerName sortby " . $sortby fhem "attr $wakeuptimerName sortby " . $sortby
if ($sortby); if ($sortby);
@ -960,132 +720,7 @@ sub GUEST_Set($@) {
return undef; return undef;
} }
############################################################################################################ # module Fn ####################################################################
#
# 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;
}
###################################
sub GUEST_SetLocation($$$;$$$$$$) { sub GUEST_SetLocation($$$;$$$$$$) {
my ( $name, $location, $trigger, $id, $time, $lat, $long, $address, my ( $name, $location, $trigger, $id, $time, $lat, $long, $address,
$device ) = @_; $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; 1;
=pod =pod

View File

@ -1,60 +1,29 @@
###############################################################################
# $Id$ # $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 <http://www.gnu.org/licenses/>.
#
##############################################################################
package main; package main;
use strict; use strict;
use warnings; use warnings;
use Time::Local;
use Data::Dumper; use Data::Dumper;
use Time::Local;
require RESIDENTStk; require RESIDENTStk;
sub ROOMMATE_Set($@); # initialize ##################################################################
sub ROOMMATE_Define($$);
sub ROOMMATE_Notify($$);
sub ROOMMATE_Attr(@);
sub ROOMMATE_Undefine($$);
###################################
sub ROOMMATE_Initialize($) { sub ROOMMATE_Initialize($) {
my ($hash) = @_; my ($hash) = @_;
Log3 $hash, 5, "ROOMMATE_Initialize: Entering";
$hash->{SetFn} = "ROOMMATE_Set";
$hash->{DefFn} = "ROOMMATE_Define"; $hash->{DefFn} = "ROOMMATE_Define";
$hash->{NotifyFn} = "ROOMMATE_Notify";
$hash->{AttrFn} = "ROOMMATE_Attr";
$hash->{UndefFn} = "ROOMMATE_Undefine"; $hash->{UndefFn} = "ROOMMATE_Undefine";
$hash->{SetFn} = "ROOMMATE_Set";
$hash->{AttrFn} = "RESIDENTStk_RG_Attr";
$hash->{NotifyFn} = "RESIDENTStk_RG_Notify";
$hash->{AttrList} = $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; . $readingFnAttributes;
} }
################################### # regular Fn ##################################################################
sub ROOMMATE_Define($$) { sub ROOMMATE_Define($$) {
my ( $hash, $def ) = @_; my ( $hash, $def ) = @_;
my @a = split( "[ \t][ \t]*", $def ); my @a = split( "[ \t][ \t]*", $def );
@ -70,6 +39,8 @@ sub ROOMMATE_Define($$) {
return $msg; return $msg;
} }
$hash->{NOTIFYDEV} = "";
$hash->{RESIDENTGROUPS} = defined( $a[2] ) ? $a[2] : ""; $hash->{RESIDENTGROUPS} = defined( $a[2] ) ? $a[2] : "";
if ( defined( $hash->{RESIDENTGROUPS} ) ) { if ( defined( $hash->{RESIDENTGROUPS} ) ) {
foreach ( split( /,/, $hash->{RESIDENTGROUPS} ) ) { foreach ( split( /,/, $hash->{RESIDENTGROUPS} ) ) {
@ -112,7 +83,7 @@ sub ROOMMATE_Define($$) {
# run timers # run timers
InternalTimer( InternalTimer(
gettimeofday() + 15, gettimeofday() + 15,
"ROOMMATE_StartInternalTimers", "RESIDENTStk_RG_StartInternalTimers",
$hash, 0 $hash, 0
); );
@ -130,86 +101,10 @@ sub ROOMMATE_Define($$) {
return undef; 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($$) { sub ROOMMATE_Undefine($$) {
my ( $hash, $name ) = @_; my ( $hash, $name ) = @_;
ROOMMATE_StopInternalTimers($hash); RESIDENTStk_RG_StopInternalTimers($hash);
if ( defined( $hash->{RESIDENTGROUPS} ) ) { if ( defined( $hash->{RESIDENTGROUPS} ) ) {
my $old = $hash->{RESIDENTGROUPS}; my $old = $hash->{RESIDENTGROUPS};
@ -226,149 +121,8 @@ sub ROOMMATE_Undefine($$) {
return undef; return undef;
} }
################################### sub ROOMMATE_Set($@);
sub ROOMMATE_Notify($$) {
my ( $hash, $dev ) = @_;
my $devName = $dev->{NAME};
my $hashName = $hash->{NAME};
return if ( IsDisabled($hashName) or IsDisabled($devName) );
# 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($@) { sub ROOMMATE_Set($@) {
my ( $hash, @a ) = @_; my ( $hash, @a ) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -708,13 +462,13 @@ sub ROOMMATE_Set($@) {
} }
# calculate duration timers # calculate duration timers
ROOMMATE_DurationTimer( $hash, $silent ); RESIDENTStk_RG_DurationTimer( $hash, $silent );
readingsEndUpdate( $hash, 1 ); readingsEndUpdate( $hash, 1 );
# enable or disable AutoGone timer # enable or disable AutoGone timer
if ( $newstate eq "absent" ) { if ( $newstate eq "absent" ) {
ROOMMATE_AutoGone($hash); RESIDENTStk_RG_AutoGone($hash);
} }
elsif ( $state eq "absent" ) { elsif ( $state eq "absent" ) {
delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} );
@ -870,7 +624,7 @@ sub ROOMMATE_Set($@) {
fhem "attr $wakeuptimerName room " . $attr{$name}{room} fhem "attr $wakeuptimerName room " . $attr{$name}{room}
if ( defined( $attr{$name}{room} ) ); if ( defined( $attr{$name}{room} ) );
fhem 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 userattr wakeupUserdevice";
fhem "attr $wakeuptimerName sortby " . $sortby fhem "attr $wakeuptimerName sortby " . $sortby
if ($sortby); if ($sortby);
@ -942,130 +696,7 @@ sub ROOMMATE_Set($@) {
return undef; return undef;
} }
############################################################################################################ # module Fn ####################################################################
#
# 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;
}
###################################
sub ROOMMATE_SetLocation($$$;$$$$$$) { sub ROOMMATE_SetLocation($$$;$$$$$$) {
my ( $name, $location, $trigger, $id, $time, $lat, $long, $address, my ( $name, $location, $trigger, $id, $time, $lat, $long, $address,
$device ) = @_; $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; 1;
=pod =pod

View File

@ -1,31 +1,10 @@
###############################################################################
# $Id$ # $Id$
############################################################################## # package main;
# # use strict;
# RESIDENTStk.pm # use warnings;
# Additional functions for 10_RESIDENTS.pm, 20_ROOMMATE.pm, 20_GUEST.pm # use Data::Dumper;
# sub RESIDENTStk_Initialize() { }
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
sub RESIDENTStk_Initialize() {
}
##################################### #####################################
# PRE-DEFINITION: wakeuptimer # PRE-DEFINITION: wakeuptimer
@ -40,20 +19,18 @@ sub RESIDENTStk_wakeupSet($$) {
my ( $a, $h ) = parseParams($n); my ( $a, $h ) = parseParams($n);
my $cmd = shift @$a; my $cmd = shift @$a;
my $VALUE = join( " ", @$a ); my $VALUE = join( " ", @$a );
my $nextRun = ReadingsVal( $NAME, "nextRun", "07:00" );
$cmd =~ s/^state:\s*(.*)$/$1/;
return if ( $cmd =~ /^[A-Za-z]+:/ );
# filter non-registered notifies # filter non-registered notifies
if ( $cmd !~ 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, Log3 $NAME, 6,
"RESIDENTStk $NAME: " "RESIDENTStk $NAME: "
. "received unspecified notify '" . "received unspecified notify '$cmd' - nothing to do";
. $cmd
. "' - nothing to do";
fhem "set $NAME nextRun $nextRun";
return; return;
} }
@ -71,7 +48,8 @@ m/^((?:next[rR]un)?\s*(off|OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?
my $wakeupResetdays = my $wakeupResetdays =
ReadingsVal( $NAME, "wakeupResetdays", ReadingsVal( $NAME, "wakeupResetdays",
AttrVal( $NAME, "wakeupResetdays", "" ) ); AttrVal( $NAME, "wakeupResetdays", "" ) );
my $wakeupOffset = AttrVal( $NAME, "wakeupOffset", 0 ); my $wakeupOffset =
ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
my $wakeupEnforced = my $wakeupEnforced =
ReadingsVal( $NAME, "wakeupEnforced", ReadingsVal( $NAME, "wakeupEnforced",
AttrVal( $NAME, "wakeupEnforced", 0 ) ); 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 $room = AttrVal( $NAME, "room", 0 );
my $userattr = AttrVal( $NAME, "userattr", 0 ); my $userattr = AttrVal( $NAME, "userattr", 0 );
my $lastRun = ReadingsVal( $NAME, "lastRun", "07:00" ); my $lastRun = ReadingsVal( $NAME, "lastRun", "07:00" );
my $nextRun = ReadingsVal( $NAME, "nextRun", "07:00" );
my $running = ReadingsVal( $NAME, "running", 0 ); my $running = ReadingsVal( $NAME, "running", 0 );
my $wakeupUserdeviceState = ReadingsVal( $wakeupUserdevice, "state", 0 ); my $wakeupUserdeviceState = ReadingsVal( $wakeupUserdevice, "state", 0 );
my $atName = "at_" . $NAME; 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 # check for required userattr attribute
my $userattributes = 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"; "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 { 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: # conditional enforced wake-up:
# only if actual wake-up time is not wakeupDefaultTime # only if actual wake-up time is not wakeupDefaultTime
if ( $wakeupEnforced == 2 elsif ($wakeupEnforced == 2
&& $wakeupDefaultTime && $wakeupDefaultTime
&& $wakeupDefaultTime ne $lastRun ) && $wakeupDefaultTime ne $lastRun )
{ {
Log3 $NAME, 4,
"RESIDENTStk $NAME: "
. "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)";
$wakeupEnforced = 1; $wakeupEnforced = 1;
} }
elsif ( $wakeupEnforced == 2 ) { elsif ( $wakeupEnforced > 1 ) {
$wakeupEnforced = 0; $wakeupEnforced = 0;
} }
@ -852,7 +850,7 @@ return;;\
# wakeup attributes # wakeup attributes
# #
elsif ( $cmd =~ elsif ( $cmd =~
m/^(wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime)$/ m/^(wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime|wakeupOffset)$/
) )
{ {
Log3 $NAME, 4, "RESIDENTStk $NAME: " . "setting $1 to '$VALUE'"; 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}, readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
"nextWakeup", $nextWakeup ); "nextWakeup", $nextWakeup );
readingsEndUpdate( $defs{$wakeupUserdevice}, 1 ); readingsEndUpdate( $defs{$wakeupUserdevice}, 1 );
} }
return undef; return undef;
@ -926,11 +923,20 @@ m/^(?:nextRun)?\s*(OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?$/i
# #
sub RESIDENTStk_wakeupGetBegin($;$) { sub RESIDENTStk_wakeupGetBegin($;$) {
my ( $NAME, $wakeupAtdevice ) = @_; 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 $nextRun = ReadingsVal( $NAME, "nextRun", 0 );
my $wakeupDefaultTime = my $wakeupDefaultTime =
ReadingsVal( $NAME, "wakeupDefaultTime", ReadingsVal( $NAME, "wakeupDefaultTime",
AttrVal( $NAME, "wakeupDefaultTime", 0 ) ); AttrVal( $NAME, "wakeupDefaultTime", 0 ) );
my $wakeupOffset = AttrVal( $NAME, "wakeupOffset", 0 ); my $wakeupOffset =
ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
my $wakeupInitTime = ( my $wakeupInitTime = (
$wakeupDefaultTime && lc($wakeupDefaultTime) ne "off" $wakeupDefaultTime && lc($wakeupDefaultTime) ne "off"
? $wakeupDefaultTime ? $wakeupDefaultTime
@ -1013,6 +1019,13 @@ sub RESIDENTStk_wakeupGetBegin($;$) {
sub RESIDENTStk_wakeupRun($;$) { sub RESIDENTStk_wakeupRun($;$) {
my ( $NAME, $forceRun ) = @_; 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 $wakeupMacro = AttrVal( $NAME, "wakeupMacro", 0 );
my $wakeupDefaultTime = my $wakeupDefaultTime =
ReadingsVal( $NAME, "wakeupDefaultTime", ReadingsVal( $NAME, "wakeupDefaultTime",
@ -1027,7 +1040,8 @@ sub RESIDENTStk_wakeupRun($;$) {
my $wakeupResetdays = my $wakeupResetdays =
ReadingsVal( $NAME, "wakeupResetdays", ReadingsVal( $NAME, "wakeupResetdays",
AttrVal( $NAME, "wakeupResetdays", "" ) ); AttrVal( $NAME, "wakeupResetdays", "" ) );
my $wakeupOffset = AttrVal( $NAME, "wakeupOffset", 0 ); my $wakeupOffset =
ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
my $wakeupEnforced = my $wakeupEnforced =
ReadingsVal( $NAME, "wakeupEnforced", ReadingsVal( $NAME, "wakeupEnforced",
AttrVal( $NAME, "wakeupEnforced", 0 ) ); AttrVal( $NAME, "wakeupEnforced", 0 ) );
@ -1092,18 +1106,10 @@ sub RESIDENTStk_wakeupRun($;$) {
if ( $wakeupResetdays ne "" ); if ( $wakeupResetdays ne "" );
my %rdays = map { $_ => 1 } @rdays; my %rdays = map { $_ => 1 } @rdays;
if ( !IsDevice($NAME) ) { if ( IsDisabled($wakeupDevice) ) {
return "$NAME: Non existing device";
}
elsif ( IsDisabled($wakeupDevice) ) {
Log3 $name, 4, Log3 $name, 4,
"RESIDENTStk $NAME: " "RESIDENTStk $NAME: "
. "device disabled - not triggering wake-up program"; . "wakeupDevice 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";
} }
elsif ( !$wakeupUserdevice ) { elsif ( !$wakeupUserdevice ) {
return "$NAME: missing attribute wakeupUserdevice"; return "$NAME: missing attribute wakeupUserdevice";
@ -1113,7 +1119,7 @@ sub RESIDENTStk_wakeupRun($;$) {
} }
elsif ( !IsDevice( $wakeupUserdevice, "RESIDENTS|ROOMMATE|GUEST" ) ) { elsif ( !IsDevice( $wakeupUserdevice, "RESIDENTS|ROOMMATE|GUEST" ) ) {
return "$NAME: " 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" ) elsif ( IsDevice( $wakeupUserdevice, "GUEST" )
&& $wakeupUserdeviceState eq "none" ) && $wakeupUserdeviceState eq "none" )
@ -1124,9 +1130,23 @@ sub RESIDENTStk_wakeupRun($;$) {
fhem "set $NAME nextRun OFF"; fhem "set $NAME nextRun OFF";
return; 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} && !$days{$today}
&& !$forceRun ) && ( $wakeupHolidays eq ""
|| $wakeupHolidays eq "andHoliday"
|| $wakeupHolidays eq "andNoHoliday" )
)
{ {
Log3 $NAME, 4, Log3 $NAME, 4,
"RESIDENTStk $NAME: " "RESIDENTStk $NAME: "
@ -1134,39 +1154,19 @@ sub RESIDENTStk_wakeupRun($;$) {
} }
elsif ( elsif (
!$forceRun !$forceRun
&& ( $wakeupHolidays eq "orHoliday"
|| $wakeupHolidays eq "orNoHoliday" )
&& ( && (
!$days{$today} ( $wakeupHolidays eq "andHoliday" && !$holidayToday )
&& ( || ( $wakeupHolidays eq "andNoHoliday"
( $wakeupHolidays eq "orHoliday" && !$holidayToday ) && $holidayToday )
|| ( $wakeupHolidays eq "orHoliday" && !$holidayToday )
|| ( $wakeupHolidays eq "orNoHoliday" || ( $wakeupHolidays eq "orNoHoliday"
&& $holidayToday ) && $holidayToday )
) )
) )
)
{ {
Log3 $NAME, 4, Log3 $NAME, 4,
"RESIDENTStk $NAME: " "RESIDENTStk $NAME: "
. "neither weekday nor holiday restriction matched - not triggering wake-up program this time"; . "holiday restriction $wakeupHolidays in use - 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";
} }
elsif ($wakeupUserdeviceState eq "absent" elsif ($wakeupUserdeviceState eq "absent"
|| $wakeupUserdeviceState eq "gone" || $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)"; . "won't trigger wake-up program due to non-expired wakeupWaitPeriod threshold since lastAwake (expLastAwake=$expLastAwake > nowRunSec=$nowRunSec)";
} }
else { else {
# conditional enforced wake-up: # conditional enforced wake-up:
# only if actual wake-up time is # only if actual wake-up time is
# earlier than wakeupDefaultTime # earlier than wakeupDefaultTime
@ -1249,7 +1250,7 @@ sub RESIDENTStk_wakeupRun($;$) {
. "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)"; . "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)";
$wakeupEnforced = 1; $wakeupEnforced = 1;
} }
elsif ( $wakeupEnforced == 2 ) { elsif ( $wakeupEnforced > 1 ) {
$wakeupEnforced = 0; $wakeupEnforced = 0;
} }
@ -1289,7 +1290,6 @@ sub RESIDENTStk_wakeupRun($;$) {
$running = 1; $running = 1;
} }
} }
} }
@ -1348,11 +1348,9 @@ sub RESIDENTStk_wakeupRun($;$) {
sub RESIDENTStk_AttrFnDummy(@) { sub RESIDENTStk_AttrFnDummy(@) {
my ( $cmd, $name, $aName, $aVal ) = @_; my ( $cmd, $name, $aName, $aVal ) = @_;
# set attribute
if ( $init_done && $cmd eq "set" ) {
# wakeupResetSwitcher # wakeupResetSwitcher
if ( $aName eq "wakeupResetSwitcher" ) { if ( $aName eq "wakeupResetSwitcher" ) {
if ( $init_done && $cmd eq "set" ) {
if ( !IsDevice($aVal) ) { if ( !IsDevice($aVal) ) {
my $alias = AttrVal( $name, "alias", 0 ); my $alias = AttrVal( $name, "alias", 0 );
my $group = AttrVal( $name, "group", 0 ); my $group = AttrVal( $name, "group", 0 );
@ -1360,7 +1358,7 @@ sub RESIDENTStk_AttrFnDummy(@) {
fhem "define $aVal dummy"; fhem "define $aVal dummy";
fhem "attr $aVal " 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) { if ($alias) {
fhem "attr $aVal alias $alias Reset"; fhem "attr $aVal alias $alias Reset";
} }
@ -1381,14 +1379,12 @@ sub RESIDENTStk_AttrFnDummy(@) {
Log3 $name, 3, Log3 $name, 3,
"RESIDENTStk $name: new slave dummy device $aVal created"; "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; return undef;
@ -1423,18 +1419,13 @@ sub RESIDENTStk_wakeupGetNext($;$) {
my $secNow = RESIDENTStk_time2sec( $hour . ":" . $min ) + $sec; my $secNow = RESIDENTStk_time2sec( $hour . ":" . $min ) + $sec;
my $definitiveNextToday; my $definitiveNextToday;
my $definitiveNextTomorrow; my $definitiveNextTomorrow;
my $definitiveNextTodayDev = 0; my $definitiveNextTodayDev;
my $definitiveNextTomorrowDev = 0; my $definitiveNextTomorrowDev;
my $holidayDevice = AttrVal( "global", "holiday2we", 0 ); my $holidayDevice = AttrVal( "global", "holiday2we", 0 );
# check for each registered wake-up device # check for each registered wake-up device
for my $wakeupDevice ( split /,/, $wakeupDeviceList ) { for my $wakeupDevice ( split /,/, $wakeupDeviceList ) {
next if !$wakeupDevice;
my $ltoday = $today;
my $ltomorrow = $tomorrow;
if ( !IsDevice($wakeupDevice) ) { if ( !IsDevice($wakeupDevice) ) {
Log3 $name, 4, Log3 $name, 4,
"RESIDENTStk $name: " "RESIDENTStk $name: "
@ -1454,25 +1445,16 @@ sub RESIDENTStk_wakeupGetNext($;$) {
next; next;
} }
elsif ( IsDisabled($wakeupDevice) ) {
Log3 $name, 4,
"RESIDENTStk $name: "
. "00 - ignoring disabled wakeupDevice $wakeupDevice";
next;
}
Log3 $name, 4, my $wakeupAtdevice = AttrVal( $wakeupDevice, "wakeupAtdevice", undef );
"RESIDENTStk $name: " my $wakeupOffset =
. "00 - checking for next wake-up candidate $wakeupDevice"; ReadingsVal( $wakeupDevice, "wakeupOffset",
AttrVal( $wakeupDevice, "wakeupOffset", 0 ) );
my $nextRun = ReadingsVal( $wakeupDevice, "nextRun", 0 );
my $wakeupAtdevice = AttrVal( $wakeupDevice, "wakeupAtdevice", 0 );
my $wakeupOffset = AttrVal( $wakeupDevice, "wakeupOffset", 0 );
my $wakeupAtNTM = ( my $wakeupAtNTM = (
IsDevice($wakeupAtdevice) IsDevice($wakeupAtdevice)
&& defined( $defs{$wakeupAtdevice}{NTM} ) && defined( $defs{$wakeupAtdevice}{NTM} )
? substr( $defs{$wakeupAtdevice}{NTM}, 0, -3 ) ? substr( $defs{$wakeupAtdevice}{NTM}, 0, -3 )
: 0 : undef
); );
my $wakeupDays = my $wakeupDays =
ReadingsVal( $wakeupDevice, "wakeupDays", ReadingsVal( $wakeupDevice, "wakeupDays",
@ -1483,11 +1465,31 @@ sub RESIDENTStk_wakeupGetNext($;$) {
my $holidayToday = 0; my $holidayToday = 0;
my $holidayTomorrow = 0; my $holidayTomorrow = 0;
my $nextRunSrc; 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 # get holiday status for today and tomorrow
if ( $wakeupHolidays ne "" if ( $wakeupHolidays eq "" ) {
&& IsDevice( $holidayDevice, "holiday" ) ) Log3 $name, 4,
{ "RESIDENTStk $wakeupDevice: 01 - Not considering any holidays";
}
elsif ( IsDevice( $holidayDevice, "holiday" ) ) {
$holidayToday = 1 $holidayToday = 1
unless ( unless (
ReadingsVal( $holidayDevice, "state", "none" ) eq "none" ); ReadingsVal( $holidayDevice, "state", "none" ) eq "none" );
@ -1497,11 +1499,7 @@ sub RESIDENTStk_wakeupGetNext($;$) {
Log3 $name, 4, Log3 $name, 4,
"RESIDENTStk $wakeupDevice: " "RESIDENTStk $wakeupDevice: "
. "01 - Holidays to be considered - today=$holidayToday tomorrow=$holidayTomorrow"; . "01 - Holidays to be considered ($wakeupHolidays) - holidayToday=$holidayToday holidayTomorrow=$holidayTomorrow";
}
else {
Log3 $name, 4,
"RESIDENTStk $wakeupDevice: 01 - Not considering any holidays";
} }
# set day scope for today # set day scope for today
@ -1516,15 +1514,6 @@ sub RESIDENTStk_wakeupGetNext($;$) {
if ( $wakeupDays ne "" ); if ( $wakeupDays ne "" );
my %daysTomorrow = map { $_ => 1 } @daysTomorrow; my %daysTomorrow = map { $_ => 1 } @daysTomorrow;
if ( lc($nextRun) eq "off"
|| $nextRun !~ /^([0-9]{2}:[0-9]{2})$/ )
{
Log3 $name, 4,
"RESIDENTStk $wakeupDevice: " . "02 - set to OFF so no candidate";
next;
}
else {
Log3 $name, 4, Log3 $name, 4,
"RESIDENTStk $wakeupDevice: " "RESIDENTStk $wakeupDevice: "
. "02 - possible candidate found - weekdayToday=$ltoday weekdayTomorrow=$ltomorrow"; . "02 - possible candidate found - weekdayToday=$ltoday weekdayTomorrow=$ltomorrow";
@ -1610,9 +1599,12 @@ sub RESIDENTStk_wakeupGetNext($;$) {
$definitiveNextToday = $nextRunSec; $definitiveNextToday = $nextRunSec;
$definitiveNextTodayDev = $wakeupDevice; $definitiveNextTodayDev = $wakeupDevice;
} }
} }
else {
elsif ($wakeupHolidays eq ""
|| $wakeupHolidays eq "andHoliday"
|| $wakeupHolidays eq "andNoHoliday" )
{
Log3 $name, 4, Log3 $name, 4,
"RESIDENTStk $wakeupDevice: " "RESIDENTStk $wakeupDevice: "
. "05 - won't be running today anymore based on weekday decision"; . "05 - won't be running today anymore based on weekday decision";
@ -1620,10 +1612,9 @@ sub RESIDENTStk_wakeupGetNext($;$) {
} }
# if we need to consider holidays in parallel to weekdays # if we need to consider holidays in parallel to weekdays
if ( ( $wakeupHolidays eq "orHoliday" && $holidayToday ) elsif (( $wakeupHolidays eq "orHoliday" && !$holidayToday )
|| ( $wakeupHolidays eq "orNoHoliday" && !$holidayToday ) ) || ( $wakeupHolidays eq "orNoHoliday" && $holidayToday ) )
{ {
Log3 $name, 4, Log3 $name, 4,
"RESIDENTStk $wakeupDevice: " "RESIDENTStk $wakeupDevice: "
. "06 - won't be running today based on holiday decision"; . "06 - won't be running today based on holiday decision";
@ -1640,7 +1631,6 @@ sub RESIDENTStk_wakeupGetNext($;$) {
$definitiveNextToday = $nextRunSec; $definitiveNextToday = $nextRunSec;
$definitiveNextTodayDev = $wakeupDevice; $definitiveNextTodayDev = $wakeupDevice;
} }
} }
# running later # running later
@ -1654,10 +1644,7 @@ sub RESIDENTStk_wakeupGetNext($;$) {
# if we need to consider holidays in addition # if we need to consider holidays in addition
if ( if (
( ( $wakeupHolidays eq "andHoliday" && !$holidayTomorrow )
$wakeupHolidays eq "andHoliday"
&& !$holidayTomorrow
)
|| ( $wakeupHolidays eq "andNoHoliday" || ( $wakeupHolidays eq "andNoHoliday"
&& $holidayTomorrow ) && $holidayTomorrow )
) )
@ -1678,9 +1665,12 @@ sub RESIDENTStk_wakeupGetNext($;$) {
$definitiveNextTomorrow = $nextRunSec; $definitiveNextTomorrow = $nextRunSec;
$definitiveNextTomorrowDev = $wakeupDevice; $definitiveNextTomorrowDev = $wakeupDevice;
} }
} }
else {
elsif ($wakeupHolidays eq ""
|| $wakeupHolidays eq "andHoliday"
|| $wakeupHolidays eq "andNoHoliday" )
{
Log3 $name, 4, Log3 $name, 4,
"RESIDENTStk $wakeupDevice: " "RESIDENTStk $wakeupDevice: "
. "05 - won't be running tomorrow based on weekday decision"; . "05 - won't be running tomorrow based on weekday decision";
@ -1688,10 +1678,10 @@ sub RESIDENTStk_wakeupGetNext($;$) {
} }
# if we need to consider holidays in parallel to weekdays # if we need to consider holidays in parallel to weekdays
if ( elsif (
( $wakeupHolidays eq "orHoliday" && $holidayTomorrow ) ( $wakeupHolidays eq "orHoliday" && !$holidayTomorrow )
|| ( $wakeupHolidays eq "orNoHoliday" || ( $wakeupHolidays eq "orNoHoliday"
&& !$holidayTomorrow ) && $holidayTomorrow )
) )
{ {
Log3 $name, 4, Log3 $name, 4,
@ -1700,6 +1690,7 @@ sub RESIDENTStk_wakeupGetNext($;$) {
next; next;
} }
# easy if there is no holiday dependency
elsif ( !$definitiveNextTomorrow elsif ( !$definitiveNextTomorrow
|| $nextRunSec < $definitiveNextTomorrow ) || $nextRunSec < $definitiveNextTomorrow )
{ {
@ -1709,9 +1700,6 @@ sub RESIDENTStk_wakeupGetNext($;$) {
$definitiveNextTomorrow = $nextRunSec; $definitiveNextTomorrow = $nextRunSec;
$definitiveNextTomorrowDev = $wakeupDevice; $definitiveNextTomorrowDev = $wakeupDevice;
} }
}
} }
if ($wakeupOffset) { if ($wakeupOffset) {
@ -1900,11 +1888,30 @@ sub RESIDENTStk_RemoveInternalTimer($$) {
} }
} }
sub RESIDENTStk_findResidentSlaves($) { sub RESIDENTStk_RG_StartInternalTimers($$) {
my ($hash) = @_; 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 return
unless ( ref($hash) eq "HASH" && defined( $hash->{NAME} ) ); unless ( ref($hash) eq "HASH" && defined( $hash->{NAME} ) );
$hash->{NOTIFYDEV} = "";
delete $hash->{ROOMMATES}; delete $hash->{ROOMMATES};
foreach ( devspec2array("TYPE=ROOMMATE") ) { foreach ( devspec2array("TYPE=ROOMMATE") ) {
next next
@ -1928,6 +1935,386 @@ sub RESIDENTStk_findResidentSlaves($) {
$hash->{GUESTS} .= "," if ( $hash->{GUESTS} ); $hash->{GUESTS} .= "," if ( $hash->{GUESTS} );
$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; 1;