mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-01 19:30:31 +00:00
9eb1ba7bb2
git-svn-id: https://svn.fhem.de/fhem/trunk@19788 2b470e98-0d58-463d-a4d8-8e2adae1ed80
4050 lines
147 KiB
Perl
4050 lines
147 KiB
Perl
###############################################################################
|
|
# $Id$
|
|
package main;
|
|
use strict;
|
|
use warnings;
|
|
use POSIX;
|
|
|
|
use FHEM::Meta;
|
|
use Time::HiRes qw(gettimeofday);
|
|
use Time::Local;
|
|
use Unit;
|
|
|
|
#use Data::Dumper;
|
|
|
|
our ( @RESIDENTStk_attr, %RESIDENTStk_types, %RESIDENTStk_subTypes );
|
|
|
|
# module variables ############################################################
|
|
@RESIDENTStk_attr = (
|
|
|
|
"autoGoneAfter:0,12,16,24,26,28,30,36,48,60",
|
|
"geofenceUUIDs",
|
|
"lang:EN,DE",
|
|
"locationHome",
|
|
"locations",
|
|
"locationUnderway",
|
|
"locationWayhome",
|
|
"moodDefault",
|
|
"moods",
|
|
"moodSleepy",
|
|
"noDuration:0,1",
|
|
"passStatusTo",
|
|
"passPresenceTo",
|
|
"presenceDevices",
|
|
"realname:group,alias",
|
|
"showAllStates:0,1",
|
|
"wakeupDevice",
|
|
|
|
);
|
|
%RESIDENTStk_types = (
|
|
en => {
|
|
ROOMMATE => 'roommate',
|
|
GUEST => 'guest',
|
|
PET => 'pet',
|
|
},
|
|
de => {
|
|
ROOMMATE => 'Bewohner',
|
|
GUEST => 'Gast',
|
|
PET => 'Haustier',
|
|
},
|
|
);
|
|
%RESIDENTStk_subTypes = (
|
|
en => {
|
|
ROOMMATE =>
|
|
[ 'baby', 'toddler', 'child', 'teenager', 'adult', 'senior' ],
|
|
GUEST => [ 'generic', 'childcare', 'domesticWorker', 'vacationer' ],
|
|
PET => [ 'generic', 'bird', 'cat', 'dog', 'monkey', 'pig' ]
|
|
},
|
|
de => {
|
|
ROOMMATE => [
|
|
'Säugling', 'Kleinkind', 'Kind', 'Teenager',
|
|
'Erwachsener', 'Senior'
|
|
],
|
|
GUEST =>
|
|
[ 'generisch', 'Kinderbetreuung', 'Hausangestellter', 'Urlaubsgast' ],
|
|
PET => [ 'generisch', 'Vogel', 'Katze', 'Hund', 'Affe', 'Schwein' ]
|
|
},
|
|
icons => {
|
|
ROOMMATE => [
|
|
'scene_baby@orange', 'scene_childs_room@orange',
|
|
'scene_childs_room@orange', 'people_sensor@orange',
|
|
'people_sensor@green', 'people_sensor@orange'
|
|
],
|
|
GUEST => [
|
|
'scene_visit_guests@orange', 'scene_childs_room@orange',
|
|
'scene_cleaning@orange', 'scene_visit_guests@orange'
|
|
],
|
|
PET => [
|
|
'dog_silhouette@green', 'dog_silhouette@green',
|
|
'dog_silhouette@green', 'dog_silhouette@green',
|
|
'dog_silhouette@green', 'dog_silhouette@green'
|
|
]
|
|
},
|
|
);
|
|
|
|
## initialize #################################################################
|
|
sub RESIDENTStk_Initialize() { }
|
|
|
|
# regular Fn ##################################################################
|
|
sub RESIDENTStk_InitializeDev($) {
|
|
my ($hash) = @_;
|
|
$hash = $defs{$hash} unless ( ref($hash) );
|
|
my $NOTIFYDEV = "global";
|
|
|
|
if ( $hash->{TYPE} eq "RESIDENTS" ) {
|
|
$NOTIFYDEV .= "," . RESIDENTStk_findResidentSlaves($hash);
|
|
}
|
|
else {
|
|
$NOTIFYDEV .= "," . RESIDENTStk_findDummySlaves($hash);
|
|
}
|
|
|
|
return 0
|
|
if ( defined( $hash->{NOTIFYDEV} ) && $hash->{NOTIFYDEV} eq $NOTIFYDEV );
|
|
|
|
$hash->{NOTIFYDEV} = $NOTIFYDEV;
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub RESIDENTStk_Define($$) {
|
|
my ( $hash, $def ) = @_;
|
|
my @a = split( "[ \t][ \t]*", $def );
|
|
my $name = $hash->{NAME};
|
|
my $name_attr;
|
|
my $TYPE = GetType($name);
|
|
my $prefix = RESIDENTStk_GetPrefixFromType($name);
|
|
|
|
if ( $TYPE ne "RESIDENTS" && int(@a) < 2 ) {
|
|
my $msg = "Wrong syntax: define <name> $TYPE [RESIDENTS-DEVICE-NAMES]";
|
|
Log3 $name, 4, $msg;
|
|
return $msg;
|
|
}
|
|
|
|
return "Invalid parameter for RESIDENTS-DEVICE-NAMES"
|
|
unless ( $TYPE ne "RESIDENTS"
|
|
|| !defined( $a[2] )
|
|
|| $a[2] =~ /^[A-Za-z\d._]+(?:,[A-Za-z\d._]*)*$/ );
|
|
|
|
# Initialize the module and the device
|
|
return $@ unless ( FHEM::Meta::SetInternals($hash) );
|
|
use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' );
|
|
|
|
$hash->{MOD_INIT} = 1;
|
|
$hash->{NOTIFYDEV} = "global";
|
|
delete $hash->{RESIDENTGROUPS} if ( $hash->{RESIDENTGROUPS} );
|
|
$hash->{RESIDENTGROUPS} = $a[2] if ( defined( $a[2] ) );
|
|
|
|
# set default settings on first define
|
|
if ( $init_done && !defined( $hash->{OLDDEF} ) ) {
|
|
RESIDENTStk_Attr( "init", $name, $prefix . "lang" );
|
|
|
|
$attr{$name}{webCmd} = "state";
|
|
|
|
if ( $TYPE eq "RESIDENTS" ) {
|
|
$attr{$name}{icon} = "control_building_filled";
|
|
$attr{$name}{room} = "Residents";
|
|
}
|
|
|
|
elsif ( $TYPE ne "RESIDENTS" ) {
|
|
my $fname = $name;
|
|
$fname =~ s/^$prefix//;
|
|
|
|
$attr{$name}{group} = $fname if ( $prefix eq "rr_" );
|
|
$attr{$name}{group} = "Guests" if ( $prefix eq "rg_" );
|
|
$attr{$name}{group} = "Pets" if ( $prefix eq "rp_" );
|
|
$attr{$name}{alias} = "Status" if ( $prefix eq "rr_" );
|
|
$attr{$name}{alias} = $fname
|
|
if ( $prefix eq "rg_" || $prefix eq "rp_" );
|
|
$attr{$name}{icon} = "people_sensor" if ( $prefix eq "rr_" );
|
|
$attr{$name}{icon} = "scene_visit_guests" if ( $prefix eq "rg_" );
|
|
$attr{$name}{icon} = "dog_silhouette" if ( $prefix eq "rp_" );
|
|
$attr{$name}{rr_realname} = "group" if ( $prefix eq "rr_" );
|
|
$attr{$name}{rg_realname} = "alias" if ( $prefix eq "rg_" );
|
|
$attr{$name}{rp_realname} = "alias" if ( $prefix eq "rp_" );
|
|
$attr{$name}{sortby} = "1";
|
|
|
|
if ( $hash->{RESIDENTGROUPS} ) {
|
|
my $firstRgr = $hash->{RESIDENTGROUPS};
|
|
$firstRgr =~ s/,[\s\S]*$//gmi;
|
|
$attr{$name}{room} = $attr{$firstRgr}{room}
|
|
if ( $attr{$firstRgr} && $attr{$firstRgr}{room} );
|
|
}
|
|
}
|
|
}
|
|
|
|
my $subtype = 'generic';
|
|
$subtype = AttrVal( $name, 'subType', 'adult' ) if ( $TYPE eq 'ROOMMATE' );
|
|
$subtype = AttrVal( $name, 'subType', 'generic' ) if ( $TYPE eq 'PET' );
|
|
$subtype = AttrVal( $name, 'subType', 'generic' ) if ( $TYPE eq 'GUEST' );
|
|
$hash->{SUBTYPE} = $subtype if ( $TYPE ne 'RESIDENTS' );
|
|
|
|
# trigger for modified objects
|
|
if ( $TYPE ne "RESIDENTS" ) {
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdateIfChanged( $hash, "state",
|
|
ReadingsVal( $name, "state", "" ) );
|
|
readingsEndUpdate( $hash, 1 );
|
|
}
|
|
|
|
# run timers
|
|
InternalTimer(
|
|
gettimeofday() + 15,
|
|
"RESIDENTStk_StartInternalTimers",
|
|
$hash, 0
|
|
);
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
sub RESIDENTStk_Undefine($$) {
|
|
my ( $hash, $name ) = @_;
|
|
|
|
RESIDENTStk_StopInternalTimers($hash);
|
|
return undef unless ($init_done);
|
|
|
|
# delete child roommates
|
|
if ( defined( $hash->{ROOMMATES} )
|
|
&& $hash->{ROOMMATES} ne "" )
|
|
{
|
|
my @registeredRoommates =
|
|
split( /,/, $hash->{ROOMMATES} );
|
|
|
|
foreach my $child (@registeredRoommates) {
|
|
fhem( "delete " . $child );
|
|
Log3 $name, 3, "RESIDENTS $name: deleted device $child";
|
|
}
|
|
}
|
|
|
|
# delete child guests
|
|
if ( defined( $hash->{GUESTS} )
|
|
&& $hash->{GUESTS} ne "" )
|
|
{
|
|
my @registeredGuests =
|
|
split( /,/, $hash->{GUESTS} );
|
|
|
|
foreach my $child (@registeredGuests) {
|
|
fhem( "delete " . $child );
|
|
Log3 $name, 3, "RESIDENTS $name: deleted device $child";
|
|
}
|
|
}
|
|
|
|
# delete child pets
|
|
if ( defined( $hash->{PETS} )
|
|
&& $hash->{PETS} ne "" )
|
|
{
|
|
my @registeredPets =
|
|
split( /,/, $hash->{PETS} );
|
|
|
|
foreach my $child (@registeredPets) {
|
|
fhem( "delete " . $child );
|
|
Log3 $name, 3, "RESIDENTS $name: deleted device $child";
|
|
}
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub RESIDENTStk_Set($@);
|
|
|
|
sub RESIDENTStk_Set($@) {
|
|
my ( $hash, @a ) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $TYPE = GetType($name);
|
|
my $SubType = defined( $hash->{SUBTYPE} ) ? $hash->{SUBTYPE} : 'generic';
|
|
my $prefix = RESIDENTStk_GetPrefixFromType($name);
|
|
my $state = ReadingsVal( $name, "state", "initialized" );
|
|
my $presence = ReadingsVal( $name, "presence", "undefined" );
|
|
my $mood = ReadingsVal( $name, "mood", "-" );
|
|
my $location = ReadingsVal( $name, "location", "undefined" );
|
|
my $roommates = ( $hash->{ROOMMATES} ? $hash->{ROOMMATES} : undef );
|
|
my $guests = ( $hash->{GUESTS} ? $hash->{GUESTS} : undef );
|
|
my $pets = ( $hash->{PETS} ? $hash->{PETS} : undef );
|
|
my $silent = 0;
|
|
|
|
return undef if ( IsDisabled($name) );
|
|
return "No Argument given" unless ( defined( $a[1] ) );
|
|
|
|
# depending on current FHEMWEB instance's allowedCommands,
|
|
# restrict set commands if there is "set-user" in it
|
|
my $adminMode = 1;
|
|
my $FWallowedCommands = 0;
|
|
$FWallowedCommands = AttrVal( $FW_wname, "allowedCommands", 0 )
|
|
if ( defined($FW_wname) );
|
|
if ( $FWallowedCommands && $FWallowedCommands =~ m/\bset-user\b/ ) {
|
|
$adminMode = 0;
|
|
return "Forbidden command: set " . $a[1]
|
|
if ( $TYPE ne "RESIDENTS" && lc( $a[1] ) eq "create" );
|
|
|
|
return "Forbidden command: set "
|
|
. $a[1]
|
|
if (
|
|
$TYPE eq "RESIDENTS"
|
|
&& ( lc( $a[1] ) eq "addroommate"
|
|
|| lc( $a[1] ) eq "addguest"
|
|
|| lc( $a[1] ) eq "addpet"
|
|
|| lc( $a[1] ) eq "removeroommate"
|
|
|| lc( $a[1] ) eq "removeguest"
|
|
|| lc( $a[1] ) eq "removepet"
|
|
|| lc( $a[1] ) eq "create" )
|
|
);
|
|
}
|
|
|
|
# states
|
|
my $states = (
|
|
defined( $attr{$name}{ $prefix . 'states' } )
|
|
? $attr{$name}{ $prefix . 'states' }
|
|
: (
|
|
defined( $attr{$name}{ $prefix . 'showAllStates' } )
|
|
&& $attr{$name}{ $prefix . 'showAllStates' } == 1
|
|
? "home,gotosleep,asleep,awoken,absent"
|
|
: "home,gotosleep,absent"
|
|
)
|
|
);
|
|
$states .= ",gone" if ( $TYPE ne "GUEST" );
|
|
$states .= ",none" if ( $TYPE eq "GUEST" );
|
|
$states = $state . "," . $states
|
|
if ( $state ne "initialized" && $states !~ /$state/ );
|
|
$states =~ s/ /,/g;
|
|
|
|
# moods
|
|
my $moods = (
|
|
defined( $attr{$name}{ $prefix . 'moods' } )
|
|
? $attr{$name}{ $prefix . 'moods' } . ",toggle"
|
|
: "calm,relaxed,happy,excited,lonely,sad,bored,stressed,uncomfortable,sleepy,angry,toggle"
|
|
);
|
|
$moods = $mood . "," . $moods if ( $moods !~ /$mood/ );
|
|
$moods =~ s/ /,/g;
|
|
|
|
# locations
|
|
my $locations = (
|
|
defined( $attr{$name}{ $prefix . 'locations' } )
|
|
? $attr{$name}{ $prefix . 'locations' }
|
|
: ""
|
|
);
|
|
if ( $locations !~ /$location/
|
|
&& $locations ne "" )
|
|
{
|
|
$locations = ":" . $location . "," . $locations;
|
|
}
|
|
elsif ( $locations ne "" ) {
|
|
$locations = ":" . $locations;
|
|
}
|
|
$locations =~ s/ /,/g;
|
|
|
|
my $usage = "Unknown argument " . $a[1] . ", choose one of state:$states";
|
|
|
|
if ( $TYPE ne "RESIDENTS" ) {
|
|
$usage .= " mood:$moods";
|
|
$usage .= " location$locations";
|
|
if ($adminMode) {
|
|
$usage .= " create:wakeuptimer";
|
|
$usage .= ",locationMap"
|
|
if ( ReadingsVal( $name, "locationLat", "-" ) ne "-"
|
|
&& ReadingsVal( $name, "locationLong", "-" ) ne "-" );
|
|
}
|
|
|
|
}
|
|
else {
|
|
if ($adminMode) {
|
|
$usage .= " addRoommate addGuest addPet";
|
|
$usage .= " removeRoommate:" . $roommates if ($roommates);
|
|
$usage .= " removeGuest:" . $guests if ($guests);
|
|
$usage .= " removePet:" . $pets if ($pets);
|
|
$usage .= " create:wakeuptimer";
|
|
}
|
|
}
|
|
|
|
# silentSet
|
|
if ( $a[1] eq "silentSet" ) {
|
|
$silent = 1;
|
|
my $first = shift @a;
|
|
$a[0] = $first;
|
|
}
|
|
|
|
# states
|
|
if ( $a[1] eq "state"
|
|
|| $a[1] eq "home"
|
|
|| $a[1] eq "gotosleep"
|
|
|| $a[1] eq "asleep"
|
|
|| $a[1] eq "awoken"
|
|
|| $a[1] eq "absent"
|
|
|| $a[1] eq "none"
|
|
|| $a[1] eq "gone" )
|
|
{
|
|
my $newstate;
|
|
|
|
# if not direct
|
|
if (
|
|
$a[1] eq "state"
|
|
&& defined( $a[2] )
|
|
&& ( $a[2] eq "home"
|
|
|| $a[2] eq "gotosleep"
|
|
|| $a[2] eq "asleep"
|
|
|| $a[2] eq "awoken"
|
|
|| $a[2] eq "absent"
|
|
|| $a[2] eq "none"
|
|
|| $a[2] eq "gone" )
|
|
)
|
|
{
|
|
$newstate = $a[2];
|
|
}
|
|
elsif ( defined( $a[2] ) ) {
|
|
return
|
|
"Invalid 2nd argument, choose one of home gotosleep asleep awoken absent gone ";
|
|
}
|
|
else {
|
|
$newstate = $a[1];
|
|
}
|
|
|
|
$newstate = "none"
|
|
if ( $newstate eq "gone" && $TYPE eq "GUEST" );
|
|
$newstate = "gone"
|
|
if ( $newstate eq "none" && $TYPE ne "GUEST" );
|
|
|
|
Log3 $name, 2, "$TYPE set $name " . $newstate if ( !$silent );
|
|
|
|
# RESIDENTS only
|
|
if ( $TYPE eq "RESIDENTS" ) {
|
|
$presence = "absent";
|
|
|
|
# loop through every roommate
|
|
if ($roommates) {
|
|
my @registeredRoommates =
|
|
split( /,/, $roommates );
|
|
|
|
foreach my $roommate (@registeredRoommates) {
|
|
fhem "set $roommate silentSet state $newstate"
|
|
if ( ReadingsVal( $roommate, "state", "initialized" ) ne
|
|
$newstate );
|
|
}
|
|
}
|
|
|
|
# loop through every pet
|
|
if ($pets) {
|
|
my @registeredPets =
|
|
split( /,/, $pets );
|
|
|
|
foreach my $pet (@registeredPets) {
|
|
fhem "set $pet silentSet state $newstate"
|
|
if ( ReadingsVal( $pet, "state", "initialized" ) ne
|
|
$newstate );
|
|
}
|
|
}
|
|
|
|
# loop through every guest
|
|
if ($guests) {
|
|
$newstate = "none" if ( $newstate eq "gone" );
|
|
|
|
my @registeredGuests =
|
|
split( /,/, $guests );
|
|
|
|
foreach my $guest (@registeredGuests) {
|
|
fhem "set $guest"
|
|
. ( $roommates || $pets ? ":FILTER=state!=none" : "" )
|
|
. " silentSet state $newstate"
|
|
if ( ReadingsVal( $guest, "state", "initialized" ) ne
|
|
$newstate );
|
|
}
|
|
}
|
|
}
|
|
|
|
# ROOMMATE+GUEST+PET: if state changed
|
|
elsif ( $state ne $newstate ) {
|
|
readingsBeginUpdate($hash);
|
|
|
|
readingsBulkUpdate( $hash, "lastState", $state );
|
|
readingsBulkUpdate( $hash, "state", $newstate );
|
|
|
|
my $datetime = TimeNow();
|
|
|
|
# reset mood
|
|
my $mood_default =
|
|
( defined( $attr{$name}{ $prefix . 'moodDefault' } ) )
|
|
? $attr{$name}{ $prefix . 'moodDefault' }
|
|
: "calm";
|
|
my $mood_sleepy =
|
|
( defined( $attr{$name}{ $prefix . 'moodSleepy' } ) )
|
|
? $attr{$name}{ $prefix . 'moodSleepy' }
|
|
: "sleepy";
|
|
|
|
if (
|
|
$mood ne "-"
|
|
&& ( $newstate eq "gone"
|
|
|| $newstate eq "none"
|
|
|| $newstate eq "absent"
|
|
|| $newstate eq "asleep" )
|
|
)
|
|
{
|
|
Log3 $name, 4,
|
|
"$TYPE $name: implicit mood change caused by state "
|
|
. $newstate;
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "mood", "-" );
|
|
}
|
|
|
|
elsif ( $mood ne $mood_sleepy
|
|
&& ( $newstate eq "gotosleep" || $newstate eq "awoken" ) )
|
|
{
|
|
Log3 $name, 4,
|
|
"$TYPE $name: implicit mood change caused by state "
|
|
. $newstate;
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "mood",
|
|
$mood_sleepy );
|
|
}
|
|
|
|
elsif ( ( $mood eq "-" || $mood eq $mood_sleepy )
|
|
&& $newstate eq "home" )
|
|
{
|
|
Log3 $name, 4,
|
|
"$TYPE $name: implicit mood change caused by state "
|
|
. $newstate;
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "mood",
|
|
$mood_default );
|
|
}
|
|
|
|
# if state is asleep, start sleep timer
|
|
readingsBulkUpdate( $hash, "lastSleep", $datetime )
|
|
if ( $newstate eq "asleep" );
|
|
|
|
# if prior state was asleep, update sleep statistics
|
|
if ( $state eq "asleep"
|
|
&& ReadingsVal( $name, "lastSleep", "" ) ne "" )
|
|
{
|
|
readingsBulkUpdate( $hash, "lastAwake", $datetime );
|
|
readingsBulkUpdate(
|
|
$hash,
|
|
"lastDurSleep",
|
|
UConv::duration(
|
|
$datetime, ReadingsVal( $name, "lastSleep", "" )
|
|
)
|
|
);
|
|
readingsBulkUpdate(
|
|
$hash,
|
|
"lastDurSleep_cr",
|
|
UConv::duration(
|
|
$datetime, ReadingsVal( $name, "lastSleep", "" ),
|
|
"min"
|
|
)
|
|
);
|
|
}
|
|
|
|
# calculate presence state
|
|
my $newpresence =
|
|
( $newstate ne "none"
|
|
&& $newstate ne "gone"
|
|
&& $newstate ne "absent" )
|
|
? "present"
|
|
: "absent";
|
|
|
|
# stop any running wakeup-timers in case state changed
|
|
my $wakeupState = ReadingsVal( $name, "wakeup", 0 );
|
|
if ( $wakeupState > 0 ) {
|
|
my $wakeupDeviceList =
|
|
AttrVal( $name, $prefix . 'wakeupDevice', 0 );
|
|
|
|
for my $wakeupDevice ( split /,/, $wakeupDeviceList ) {
|
|
next if !$wakeupDevice;
|
|
|
|
if ( IsDevice( $wakeupDevice, "dummy" ) ) {
|
|
|
|
# forced-stop only if resident is not present anymore
|
|
if ( $newpresence eq "present" ) {
|
|
Log3 $name, 4,
|
|
"$TYPE $name: ending wakeup-timer $wakeupDevice";
|
|
fhem "set $wakeupDevice:FILTER=running!=0 end";
|
|
}
|
|
else {
|
|
Log3 $name, 4,
|
|
"$TYPE $name: stopping wakeup-timer $wakeupDevice";
|
|
fhem "set $wakeupDevice:FILTER=running!=0 stop";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# if presence changed
|
|
if ( $newpresence ne $presence ) {
|
|
readingsBulkUpdate( $hash, "presence", $newpresence );
|
|
|
|
# update location
|
|
my @location_home =
|
|
split( ' ',
|
|
AttrVal( $name, $prefix . 'locationHome', "home" ) );
|
|
my @location_underway =
|
|
split(
|
|
' ',
|
|
AttrVal( $name, $prefix . 'locationUnderway', "underway" )
|
|
);
|
|
my @location_wayhome =
|
|
split( ' ',
|
|
AttrVal( $name, $prefix . 'locationWayhome', "wayhome" ) );
|
|
my $searchstring = quotemeta($location);
|
|
|
|
if ( !$silent && $newpresence eq "present" ) {
|
|
if ( !grep( m/^$searchstring$/, @location_home )
|
|
&& $location ne $location_home[0] )
|
|
{
|
|
Log3 $name, 4,
|
|
"$TYPE $name: implicit location change caused by state "
|
|
. $newstate;
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "location",
|
|
$location_home[0] );
|
|
}
|
|
}
|
|
else {
|
|
if ( !$silent
|
|
&& !grep( m/^$searchstring$/, @location_underway )
|
|
&& $location ne $location_underway[0] )
|
|
{
|
|
Log3 $name, 4,
|
|
"$TYPE $name: implicit location change caused by state "
|
|
. $newstate;
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "location",
|
|
$location_underway[0] );
|
|
}
|
|
}
|
|
|
|
# reset wayhome
|
|
readingsBulkUpdateIfChanged( $hash, "wayhome", "0" );
|
|
|
|
# update statistics
|
|
if ( $newpresence eq "present" ) {
|
|
readingsBulkUpdate( $hash, "lastArrival", $datetime );
|
|
|
|
# absence duration
|
|
if ( ReadingsVal( $name, "lastDeparture", "-" ) ne "-" ) {
|
|
readingsBulkUpdate(
|
|
$hash,
|
|
"lastDurAbsence",
|
|
UConv::duration(
|
|
$datetime,
|
|
ReadingsVal( $name, "lastDeparture", "-" )
|
|
)
|
|
);
|
|
readingsBulkUpdate(
|
|
$hash,
|
|
"lastDurAbsence_cr",
|
|
UConv::duration(
|
|
$datetime,
|
|
ReadingsVal( $name, "lastDeparture", "-" ),
|
|
"min"
|
|
)
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
readingsBulkUpdate( $hash, "lastDeparture", $datetime );
|
|
|
|
# presence duration
|
|
if ( ReadingsVal( $name, "lastArrival", "-" ) ne "-" ) {
|
|
readingsBulkUpdate(
|
|
$hash,
|
|
"lastDurPresence",
|
|
UConv::duration(
|
|
$datetime,
|
|
ReadingsVal( $name, "lastArrival", "-" )
|
|
)
|
|
);
|
|
readingsBulkUpdate(
|
|
$hash,
|
|
"lastDurPresence_cr",
|
|
UConv::duration(
|
|
$datetime,
|
|
ReadingsVal( $name, "lastArrival", "-" ), "min"
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
# adjust linked objects
|
|
if ( defined( $attr{$name}{ $prefix . 'passPresenceTo' } )
|
|
&& $attr{$name}{ $prefix . 'passPresenceTo' } ne "" )
|
|
{
|
|
my @linkedObjects =
|
|
split( ' ', $attr{$name}{ $prefix . 'passPresenceTo' } );
|
|
|
|
foreach my $object (@linkedObjects) {
|
|
if ( IsDevice( $object, "ROOMMATE|GUEST|PET" )
|
|
&& $defs{$object}{NAME} ne $name
|
|
&& ReadingsVal( $object, "state", "" ) ne "gone"
|
|
&& ReadingsVal( $object, "state", "" ) ne "none" )
|
|
{
|
|
fhem("set $object $newstate");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
# adjust linked objects
|
|
if ( defined( $attr{$name}{ $prefix . 'passStatusTo' } )
|
|
&& $attr{$name}{ $prefix . 'passStatusTo' } ne "" )
|
|
{
|
|
my @linkedObjects =
|
|
split( ' ', $attr{$name}{ $prefix . 'passStatusTo' } );
|
|
|
|
foreach my $object (@linkedObjects) {
|
|
if ( IsDevice( $object, "ROOMMATE|GUEST|PET" )
|
|
&& $defs{$object}{NAME} ne $name
|
|
&& ReadingsVal( $object, "state", "" ) ne "absent"
|
|
&& ReadingsVal( $object, "state", "" ) ne "gone"
|
|
&& ReadingsVal( $object, "state", "" ) ne "none" )
|
|
{
|
|
fhem("set $object $newstate");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# clear readings if guest is gone
|
|
if ( $newstate eq "none"
|
|
&& $TYPE eq "GUEST"
|
|
&& $SubType eq 'generic' )
|
|
{
|
|
readingsBulkUpdate( $hash, "lastArrival", "-" )
|
|
if ( ReadingsVal( $name, "lastArrival", "-" ) ne "-" );
|
|
readingsBulkUpdate( $hash, "lastAwake", "-" )
|
|
if ( ReadingsVal( $name, "lastAwake", "-" ) ne "-" );
|
|
readingsBulkUpdate( $hash, "lastDurAbsence", "-" )
|
|
if ( ReadingsVal( $name, "lastDurAbsence", "-" ) ne "-" );
|
|
readingsBulkUpdate( $hash, "lastDurSleep", "-" )
|
|
if ( ReadingsVal( $name, "lastDurSleep", "-" ) ne "-" );
|
|
readingsBulkUpdate( $hash, "lastLocation", "-" )
|
|
if ( ReadingsVal( $name, "lastLocation", "-" ) ne "-" );
|
|
readingsBulkUpdate( $hash, "lastSleep", "-" )
|
|
if ( ReadingsVal( $name, "lastSleep", "-" ) ne "-" );
|
|
readingsBulkUpdate( $hash, "lastMood", "-" )
|
|
if ( ReadingsVal( $name, "lastMood", "-" ) ne "-" );
|
|
readingsBulkUpdate( $hash, "location", "-" )
|
|
if ( ReadingsVal( $name, "location", "-" ) ne "-" );
|
|
readingsBulkUpdate( $hash, "mood", "-" )
|
|
if ( ReadingsVal( $name, "mood", "-" ) ne "-" );
|
|
}
|
|
|
|
# calculate duration timers
|
|
RESIDENTStk_DurationTimer( $hash, $silent );
|
|
|
|
readingsEndUpdate( $hash, 1 );
|
|
|
|
# enable or disable AutoGone timer
|
|
if ( $newstate eq "absent" ) {
|
|
RESIDENTStk_AutoGone($hash);
|
|
}
|
|
elsif ( $state eq "absent" ) {
|
|
delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} );
|
|
RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash );
|
|
}
|
|
}
|
|
}
|
|
|
|
# RESIDENTS only: addRoommate
|
|
elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "addroommate" ) {
|
|
Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
|
|
if ( defined( $a[2] ) );
|
|
|
|
my $rr_name;
|
|
my $rr_name_attr;
|
|
|
|
if ( $a[2] ne "" ) {
|
|
$rr_name = "rr_" unless ( $a[2] =~ /^rr_/ );
|
|
$rr_name .= $a[2];
|
|
|
|
# define roommate
|
|
fhem( "define " . $rr_name . " ROOMMATE " . $name )
|
|
unless ( IsDevice($rr_name) );
|
|
|
|
if ( IsDevice($rr_name) ) {
|
|
return "Can't create, device $rr_name already existing."
|
|
unless ( IsDevice( $rr_name, "ROOMMATE" ) );
|
|
|
|
my $lang =
|
|
$a[3]
|
|
? uc( $a[3] )
|
|
: AttrVal( $rr_name, "rr_lang",
|
|
AttrVal( $name, "rgr_lang", undef ) );
|
|
fhem( "attr " . $rr_name . " rr_lang " . $lang )
|
|
if ($lang);
|
|
|
|
$attr{$rr_name}{comment} = "Auto-created by $name"
|
|
unless ( defined( $attr{$rr_name}{comment} )
|
|
&& $attr{$rr_name}{comment} eq "Auto-created by $name" );
|
|
|
|
fhem "sleep 1;set $rr_name silentSet state home";
|
|
Log3 $name, 3, "$TYPE $name: created new device $rr_name";
|
|
}
|
|
}
|
|
else {
|
|
return "No Argument given, choose one of name ";
|
|
}
|
|
}
|
|
|
|
# RESIDENTS only: removeRoommate
|
|
elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "removeroommate" ) {
|
|
Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
|
|
if ( defined( $a[2] ) );
|
|
|
|
if ( $a[2] ne "" ) {
|
|
my $rr_name = $a[2];
|
|
|
|
# delete roommate
|
|
if ( IsDevice($rr_name) ) {
|
|
Log3 $name, 3, "$TYPE $name: deleted device $rr_name"
|
|
if fhem( "delete " . $rr_name );
|
|
}
|
|
}
|
|
else {
|
|
return "No Argument given, choose one of name ";
|
|
}
|
|
}
|
|
|
|
# RESIDENTS only: addGuest
|
|
elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "addguest" ) {
|
|
Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
|
|
if ( defined( $a[2] ) );
|
|
|
|
my $rg_name;
|
|
my $rg_name_attr;
|
|
|
|
if ( $a[2] ne "" ) {
|
|
$rg_name = "rg_" unless ( $a[2] =~ /^rg_/ );
|
|
$rg_name .= $a[2];
|
|
|
|
# define guest
|
|
fhem( "define " . $rg_name . " GUEST " . $name )
|
|
unless ( IsDevice($rg_name) );
|
|
|
|
if ( IsDevice($rg_name) ) {
|
|
return "Can't create, device $rg_name already existing."
|
|
unless ( IsDevice( $rg_name, "GUEST" ) );
|
|
|
|
my $lang =
|
|
$a[3]
|
|
? uc( $a[3] )
|
|
: AttrVal( $rg_name, "rg_lang",
|
|
AttrVal( $name, "rgr_lang", undef ) );
|
|
fhem( "attr " . $rg_name . " rg_lang " . $lang )
|
|
if ($lang);
|
|
|
|
$attr{$rg_name}{comment} = "Auto-created by $name"
|
|
unless ( defined( $attr{$rg_name}{comment} )
|
|
&& $attr{$rg_name}{comment} eq "Auto-created by $name" );
|
|
|
|
fhem "sleep 1;set $rg_name silentSet state home";
|
|
Log3 $name, 3, "$TYPE $name: created new device $rg_name";
|
|
}
|
|
}
|
|
else {
|
|
return "No Argument given, choose one of name ";
|
|
}
|
|
}
|
|
|
|
# RESIDENTS only: removeGuest
|
|
elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "removeguest" ) {
|
|
Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
|
|
if ( defined( $a[2] ) );
|
|
|
|
if ( $a[2] ne "" ) {
|
|
my $rg_name = $a[2];
|
|
|
|
# delete guest
|
|
if ( IsDevice($rg_name) ) {
|
|
Log3 $name, 3, "$TYPE $name: deleted device $rg_name"
|
|
if fhem( "delete " . $rg_name );
|
|
}
|
|
}
|
|
else {
|
|
return "No Argument given, choose one of name ";
|
|
}
|
|
}
|
|
|
|
# RESIDENTS only: addPet
|
|
elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "addpet" ) {
|
|
Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
|
|
if ( defined( $a[2] ) );
|
|
|
|
my $rp_name;
|
|
my $rp_name_attr;
|
|
|
|
if ( $a[2] ne "" ) {
|
|
$rp_name = "rp_" unless ( $a[2] =~ /^rp_/ );
|
|
$rp_name .= $a[2];
|
|
|
|
# define pet
|
|
fhem( "define " . $rp_name . " PET " . $name )
|
|
unless ( IsDevice($rp_name) );
|
|
|
|
if ( IsDevice($rp_name) ) {
|
|
return "Can't create, device $rp_name already existing."
|
|
unless ( IsDevice( $rp_name, "PET" ) );
|
|
|
|
my $lang =
|
|
$a[3]
|
|
? uc( $a[3] )
|
|
: AttrVal( $rp_name, "rp_lang",
|
|
AttrVal( $name, "rgr_lang", undef ) );
|
|
fhem( "attr " . $rp_name . " rp_lang " . $lang )
|
|
if ($lang);
|
|
|
|
$attr{$rp_name}{comment} = "Auto-created by $name"
|
|
unless ( defined( $attr{$rp_name}{comment} )
|
|
&& $attr{$rp_name}{comment} eq "Auto-created by $name" );
|
|
|
|
fhem "sleep 1;set $rp_name silentSet state home";
|
|
Log3 $name, 3, "$TYPE $name: created new device $rp_name";
|
|
}
|
|
}
|
|
else {
|
|
return "No Argument given, choose one of name ";
|
|
}
|
|
}
|
|
|
|
# RESIDENTS only: removePet
|
|
elsif ( $TYPE eq "RESIDENTS" && lc( $a[1] ) eq "removepet" ) {
|
|
Log3 $name, 2, "$TYPE set $name " . $a[1] . " " . $a[2]
|
|
if ( defined( $a[2] ) );
|
|
|
|
if ( $a[2] ne "" ) {
|
|
my $rp_name = $a[2];
|
|
|
|
# delete pet
|
|
if ( IsDevice($rp_name) ) {
|
|
Log3 $name, 3, "$TYPE $name: deleted device $rp_name"
|
|
if fhem( "delete " . $rp_name );
|
|
}
|
|
}
|
|
else {
|
|
return "No Argument given, choose one of name ";
|
|
}
|
|
}
|
|
|
|
# mood
|
|
elsif ( $TYPE ne "RESIDENTS" && $a[1] eq "mood" ) {
|
|
if ( defined( $a[2] ) && $a[2] ne "" ) {
|
|
Log3 $name, 2, "$TYPE set $name mood " . $a[2] if ( !$silent );
|
|
readingsBeginUpdate($hash) if ( !$silent );
|
|
|
|
if ( $a[2] eq "toggle"
|
|
&& ReadingsVal( $name, "lastMood", "" ) ne "" )
|
|
{
|
|
readingsBulkUpdate( $hash, "mood",
|
|
ReadingsVal( $name, "lastMood", "" ) );
|
|
readingsBulkUpdate( $hash, "lastMood", $mood );
|
|
}
|
|
elsif ( $mood ne $a[2] ) {
|
|
readingsBulkUpdate( $hash, "lastMood", $mood )
|
|
if ( $mood ne "-" );
|
|
readingsBulkUpdate( $hash, "mood", $a[2] );
|
|
}
|
|
|
|
readingsEndUpdate( $hash, 1 ) if ( !$silent );
|
|
}
|
|
else {
|
|
return "Invalid 2nd argument, choose one of mood toggle";
|
|
}
|
|
}
|
|
|
|
# location
|
|
elsif ( $TYPE ne "RESIDENTS" && $a[1] eq "location" ) {
|
|
if ( defined( $a[2] ) && $a[2] ne "" ) {
|
|
Log3 $name, 2, "$TYPE set $name location " . $a[2]
|
|
if ( !$silent );
|
|
|
|
if ( $location ne $a[2] ) {
|
|
my $searchstring;
|
|
|
|
readingsBeginUpdate($hash) if ( !$silent );
|
|
|
|
# read attributes
|
|
my @location_home =
|
|
split( ' ',
|
|
AttrVal( $name, $prefix . 'locationHome', "home" ) );
|
|
my @location_underway =
|
|
split(
|
|
' ',
|
|
AttrVal( $name, $prefix . 'locationUnderway', "underway" )
|
|
);
|
|
my @location_wayhome =
|
|
split( ' ',
|
|
AttrVal( $name, $prefix . 'locationWayhome', "wayhome" ) );
|
|
|
|
$searchstring = quotemeta($location);
|
|
readingsBulkUpdate( $hash, "lastLocation", $location )
|
|
if ( $location ne "wayhome"
|
|
&& !grep( m/^$searchstring$/, @location_underway ) );
|
|
readingsBulkUpdate( $hash, "location", $a[2] )
|
|
if ( $a[2] ne "wayhome" );
|
|
|
|
# wayhome detection
|
|
$searchstring = quotemeta($location);
|
|
if (
|
|
(
|
|
$a[2] eq "wayhome"
|
|
|| grep( m/^$searchstring$/, @location_wayhome )
|
|
)
|
|
&& ( $presence eq "absent" )
|
|
)
|
|
{
|
|
Log3 $name, 3,
|
|
"$TYPE $name: on way back home from $location";
|
|
readingsBulkUpdateIfChanged( $hash, "wayhome", "1" );
|
|
}
|
|
|
|
readingsEndUpdate( $hash, 1 ) if ( !$silent );
|
|
|
|
# auto-updates
|
|
$searchstring = quotemeta( $a[2] );
|
|
if (
|
|
(
|
|
$a[2] eq "home"
|
|
|| grep( m/^$searchstring$/, @location_home )
|
|
)
|
|
&& $state ne "home"
|
|
&& $state ne "gotosleep"
|
|
&& $state ne "asleep"
|
|
&& $state ne "awoken"
|
|
&& $state ne "initialized"
|
|
)
|
|
{
|
|
Log3 $name, 4,
|
|
"$TYPE $name: implicit state change caused by location "
|
|
. $a[2];
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "state",
|
|
"home" );
|
|
}
|
|
elsif (
|
|
(
|
|
$a[2] eq "underway"
|
|
|| grep( m/^$searchstring$/, @location_underway )
|
|
)
|
|
&& $state ne "gone"
|
|
&& $state ne "none"
|
|
&& $state ne "absent"
|
|
&& $state ne "initialized"
|
|
)
|
|
{
|
|
Log3 $name, 4,
|
|
"$TYPE $name: implicit state change caused by location "
|
|
. $a[2];
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "state",
|
|
"absent" );
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return "Invalid 2nd argument, choose one of location ";
|
|
}
|
|
}
|
|
|
|
# create
|
|
elsif ( lc( $a[1] ) eq "create" ) {
|
|
|
|
if ( $TYPE eq "RESIDENTS"
|
|
&& ( !defined( $a[2] ) || lc( $a[2] ) ne "wakeuptimer" ) )
|
|
{
|
|
return "Invalid 2nd argument, choose one of wakeuptimer ";
|
|
}
|
|
elsif ( !defined( $a[2] ) || $a[2] !~ /^(wakeuptimer|locationMap)$/i ) {
|
|
return
|
|
"Invalid 2nd argument, choose one of wakeuptimer locationMap ";
|
|
}
|
|
|
|
elsif ( lc( $a[2] ) eq "wakeuptimer" ) {
|
|
my $i = "1";
|
|
my $wakeuptimerName = $name . "_wakeuptimer" . $i;
|
|
my $created = 0;
|
|
|
|
until ($created) {
|
|
if ( IsDevice($wakeuptimerName) ) {
|
|
$i++;
|
|
$wakeuptimerName = $name . "_wakeuptimer" . $i;
|
|
}
|
|
else {
|
|
my $sortby = AttrVal( $name, "sortby", -1 );
|
|
$sortby++;
|
|
|
|
# create new dummy device
|
|
fhem "define $wakeuptimerName dummy";
|
|
fhem "attr $wakeuptimerName alias Wake-up Timer $i";
|
|
fhem
|
|
"attr $wakeuptimerName comment Auto-created by $TYPE module for use with RESIDENTS Toolkit";
|
|
fhem
|
|
"attr $wakeuptimerName devStateIcon OFF:general_aus\@red:reset running:general_an\@green:stop .*:general_an\@orange:nextRun%20OFF";
|
|
fhem "attr $wakeuptimerName group " . $attr{$name}{group}
|
|
if ( defined( $attr{$name}{group} ) );
|
|
fhem "attr $wakeuptimerName icon time_timer";
|
|
fhem "attr $wakeuptimerName room " . $attr{$name}{room}
|
|
if ( defined( $attr{$name}{room} ) );
|
|
fhem
|
|
"attr $wakeuptimerName setList nextRun:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 reset:noArg trigger:noArg start:noArg stop:noArg end:noArg wakeupOffset:slider,0,1,120 wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:,andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3";
|
|
fhem "attr $wakeuptimerName userattr wakeupUserdevice";
|
|
fhem "attr $wakeuptimerName sortby " . $sortby
|
|
if ($sortby);
|
|
fhem "attr $wakeuptimerName wakeupUserdevice $name";
|
|
fhem "attr $wakeuptimerName webCmd nextRun";
|
|
|
|
# register slave device
|
|
my $wakeupDevice =
|
|
AttrVal( $name, $prefix . 'wakeupDevice', 0 );
|
|
if ( !$wakeupDevice ) {
|
|
fhem "attr $name $prefix"
|
|
. "wakeupDevice $wakeuptimerName";
|
|
}
|
|
elsif ( $wakeupDevice !~ /(.*,?)($wakeuptimerName)(.*,?)/ )
|
|
{
|
|
fhem "attr $name $prefix"
|
|
. "wakeupDevice "
|
|
. $wakeupDevice
|
|
. ",$wakeuptimerName";
|
|
}
|
|
|
|
# trigger first update
|
|
fhem "sleep 1;set $wakeuptimerName nextRun OFF";
|
|
|
|
$created = 1;
|
|
}
|
|
}
|
|
|
|
return
|
|
"Dummy $wakeuptimerName and other pending devices created and pre-configured.\nYou may edit Macro_$wakeuptimerName to define your wake-up actions\nand at_$wakeuptimerName for optional at-device adjustments.";
|
|
}
|
|
|
|
elsif ( $TYPE ne "RESIDENTS" && lc( $a[2] ) eq "locationmap" ) {
|
|
my $locationmapName = $name . "_map";
|
|
|
|
if ( IsDevice($locationmapName) ) {
|
|
return
|
|
"Device $locationmapName existing already, delete it first to have it re-created.";
|
|
}
|
|
else {
|
|
my $sortby = AttrVal( $name, "sortby", -1 );
|
|
$sortby++;
|
|
|
|
# create new weblink device
|
|
fhem "define $locationmapName weblink htmlCode {
|
|
'<ul style=\"width: 400px;; overflow: hidden;; height: 300px;;\">
|
|
<iframe name=\"$locationmapName\" src=\"https://www.google.com/maps/embed/v1/place?key=AIzaSyB66DvcpbXJ5eWgIkzxpUN2s_9l3_6fegM&q='
|
|
.ReadingsVal('$name','locationLat','')
|
|
.','
|
|
.ReadingsVal('$name','locationLong','')
|
|
.'&zoom=13\" width=\"480\" height=\"480\" frameborder=\"0\" style=\"border:0;; margin-top: -165px;; margin-left: -135px;;\">
|
|
</iframe>
|
|
</ul>'
|
|
}";
|
|
fhem "attr $locationmapName alias Current Location";
|
|
fhem
|
|
"attr $locationmapName comment Auto-created by $TYPE module";
|
|
fhem "attr $locationmapName group " . $attr{$name}{group}
|
|
if ( defined( $attr{$name}{group} ) );
|
|
fhem "attr $locationmapName room " . $attr{$name}{room}
|
|
if ( defined( $attr{$name}{room} ) );
|
|
}
|
|
|
|
return "Weblink device $locationmapName was created.";
|
|
}
|
|
}
|
|
|
|
# return usage hint
|
|
else {
|
|
return $usage;
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub RESIDENTStk_Attr(@) {
|
|
my ( $cmd, $name, $attribute, $value ) = @_;
|
|
my $hash = $defs{$name};
|
|
my $TYPE = GetType($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 =~
|
|
m/^([a-zA-Z\d._]+(:[A-Za-z\d_\.\-\/]+)?,?)([a-zA-Z\d._]+(:[A-Za-z\d_\.\-\/]+)?,?)*$/
|
|
);
|
|
}
|
|
|
|
elsif ( $attribute eq "subType" ) {
|
|
return "invalid value $value"
|
|
unless (
|
|
$cmd eq "del"
|
|
|| defined( $RESIDENTStk_subTypes{en}{$TYPE} ) && grep m/^$value$/,
|
|
@{ $RESIDENTStk_subTypes{en}{$TYPE} }
|
|
);
|
|
if ( $cmd eq "del" ) {
|
|
$hash->{SUBTYPE} = 'generic'
|
|
if ( $TYPE eq 'GUEST' || $TYPE eq 'PET' );
|
|
$hash->{SUBTYPE} = 'adult' if ( $TYPE eq 'ROOMMATE' );
|
|
}
|
|
else {
|
|
$hash->{SUBTYPE} = $value;
|
|
}
|
|
}
|
|
|
|
elsif ( !$init_done ) {
|
|
return undef;
|
|
}
|
|
|
|
elsif ( $attribute eq "rgr_homeAloneInStatus" ) {
|
|
return "invalid value $value"
|
|
unless ( $cmd eq "del" || $value eq '0' || $value eq '1' );
|
|
}
|
|
|
|
elsif ( $attribute eq "disable" ) {
|
|
if ( $value and $value == 1 ) {
|
|
$hash->{STATE} = "disabled";
|
|
RESIDENTStk_StopInternalTimers($hash);
|
|
}
|
|
elsif ( $cmd eq "del" or !$value ) {
|
|
evalStateFormat($hash);
|
|
RESIDENTStk_StartInternalTimers( $hash, 1 );
|
|
}
|
|
}
|
|
|
|
elsif ( $prefix ne "rgr_" && $attribute eq $prefix . "autoGoneAfter" ) {
|
|
if ($value) {
|
|
RESIDENTStk_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_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" ) {
|
|
if ( $prefix eq "rgr_" ) {
|
|
$attr{$name}{alias} = "Bewohner"
|
|
if ( !defined( $attr{$name}{alias} )
|
|
|| $attr{$name}{alias} eq "Residents" );
|
|
$attr{$name}{group} = "Zuhause Status"
|
|
if ( !defined( $attr{$name}{group} )
|
|
|| $attr{$name}{group} eq "Home State" );
|
|
}
|
|
|
|
$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" ) {
|
|
if ( $prefix eq "rgr_" ) {
|
|
$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 "Zuhause Status" );
|
|
}
|
|
|
|
$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_Notify($$) {
|
|
my ( $hash, $dev ) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $TYPE = GetType($name);
|
|
my $prefix = RESIDENTStk_GetPrefixFromType($name);
|
|
my $devName = $dev->{NAME};
|
|
my $devPrefix = RESIDENTStk_GetPrefixFromType($devName);
|
|
my $devType = GetType($devName);
|
|
|
|
if ( $devName eq "global" ) {
|
|
my $events = deviceEvents( $dev, 1 );
|
|
return "" unless ($events);
|
|
|
|
foreach ( @{$events} ) {
|
|
|
|
next if ( $_ =~ m/^[A-Za-z\d_-]+:/ );
|
|
|
|
# module and device initialization
|
|
if ( $_ =~ m/^INITIALIZED|REREADCFG$/ ) {
|
|
if ( !defined( &{'DoInitDev'} ) ) {
|
|
if ( $_ eq "REREADCFG" ) {
|
|
delete $modules{$TYPE}{READY};
|
|
delete $modules{$TYPE}{INIT};
|
|
}
|
|
RESIDENTStk_DoInitDev(
|
|
devspec2array("TYPE=$TYPE:FILTER=MOD_INIT=.+") );
|
|
}
|
|
}
|
|
|
|
# if any of our monitored devices was modified,
|
|
# recalculate monitoring status
|
|
elsif ( $_ =~
|
|
m/^(DEFINED|MODIFIED|RENAMED|DELETED)\s+([A-Za-z\d_-]+)$/ )
|
|
{
|
|
if ( defined( &{'DoInitDev'} ) ) {
|
|
|
|
# DELETED would normally be handled by fhem.pl and imply
|
|
# DoModuleTrigger instead of DoInitDev to update module
|
|
# init state
|
|
next if ( $_ =~ /^DELETED/ );
|
|
DoInitDev($name);
|
|
}
|
|
else {
|
|
|
|
# for DELETED, we normally would want to use
|
|
# DoModuleTrigger() but we miss the deleted
|
|
# device's TYPE at this state :-(
|
|
RESIDENTStk_DoInitDev($name);
|
|
}
|
|
}
|
|
|
|
# only process attribute events
|
|
next
|
|
unless ( $_ =~
|
|
m/^((?:DELETE)?ATTR)\s+([A-Za-z\d._]+)\s+([A-Za-z\d_\.\-\/]+)(?:\s+(.*)\s*)?$/
|
|
);
|
|
|
|
my $cmd = $1;
|
|
my $d = $2;
|
|
my $attr = $3;
|
|
my $val = $4;
|
|
my $type = GetType($d);
|
|
|
|
# filter attributes to be processed
|
|
next
|
|
unless ( $attr eq $prefix . "wakeupDevice"
|
|
|| $attr eq $prefix . "presenceDevices"
|
|
|| $attr eq "wakeupResetSwitcher" );
|
|
|
|
# when attributes of RESIDENTS, ROOMMATE, GUEST or PET were changed
|
|
if ( $d eq $name ) {
|
|
if ( defined( &{'DoInitDev'} ) ) {
|
|
DoInitDev($name)
|
|
if ( $TYPE ne "dummy" );
|
|
}
|
|
else {
|
|
RESIDENTStk_DoInitDev($name)
|
|
if ( $TYPE ne "dummy" );
|
|
}
|
|
return "";
|
|
}
|
|
|
|
# when slave attributes where changed
|
|
elsif ( $hash->{NOTIFYDEV} ) {
|
|
my @ndlarr = devspec2array( $hash->{NOTIFYDEV} );
|
|
return "" unless ( grep( $_ eq $d, @ndlarr ) );
|
|
|
|
# wakeupResetSwitcher
|
|
if ( $attr eq "wakeupResetSwitcher" ) {
|
|
if ( $cmd eq "ATTR" ) {
|
|
if ( !IsDevice($val) ) {
|
|
my $alias = AttrVal( $d, "alias", undef );
|
|
my $group = AttrVal( $d, "group", undef );
|
|
my $room = AttrVal( $d, "room", undef );
|
|
|
|
fhem "define $val dummy";
|
|
$attr{$val}{comment} =
|
|
"Auto-created by RESIDENTS Toolkit:"
|
|
. " easy switch between on/off for auto time reset of wake-up timer $d";
|
|
if ($alias) {
|
|
$attr{$val}{alias} = "$alias Reset";
|
|
}
|
|
else {
|
|
$attr{$val}{alias} = "Wake-up Timer Reset";
|
|
}
|
|
$attr{$val}{devStateIcon} =
|
|
"auto:time_automatic:off "
|
|
. "off:time_manual_mode:auto";
|
|
$attr{$val}{group} = "$group"
|
|
if ($group);
|
|
$attr{$val}{icon} = "refresh";
|
|
$attr{$val}{room} = "$room"
|
|
if ($room);
|
|
$attr{$val}{setList} = "state:auto,off";
|
|
$attr{$val}{webCmd} = "state";
|
|
fhem "set $val auto";
|
|
|
|
Log3 $d, 3,
|
|
"RESIDENTStk $d: "
|
|
. "new slave dummy device $val created";
|
|
}
|
|
}
|
|
|
|
if ( defined( &{'DoInitDev'} ) ) {
|
|
DoInitDev($name);
|
|
}
|
|
else {
|
|
RESIDENTStk_DoInitDev($name);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
return "" if ( IsDisabled($name) or IsDisabled($devName) );
|
|
|
|
# process events from ROOMMATE, GUEST or PET devices
|
|
# only when they hit RESIDENTS devices
|
|
if ( $TYPE eq "RESIDENTS" && $devType =~ /^ROOMMATE|GUEST|PET$/ ) {
|
|
|
|
my $events = deviceEvents( $dev, 1 );
|
|
return "" unless ($events);
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
foreach my $event ( @{$events} ) {
|
|
next unless ( defined($event) );
|
|
|
|
# state changed
|
|
if ( $event !~ /^[a-zA-Z\d._]+:/
|
|
|| $event =~ /^state:/
|
|
|| $event =~ /^wayhome:/
|
|
|| $event =~ /^wakeup:/ )
|
|
{
|
|
RESIDENTS_UpdateReadings($hash);
|
|
}
|
|
|
|
# activity
|
|
if ( $event !~ /^[a-zA-Z\d._]+:/ || $event =~ /^state:/ ) {
|
|
|
|
# get user realname
|
|
my $realname =
|
|
AttrVal( $devName,
|
|
AttrVal( $devName, $devPrefix . "realname", "group" ),
|
|
$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);
|
|
|
|
my @registeredWakeupdevs =
|
|
split( ',', AttrVal( $name, $prefix . "wakeupDevice", "" ) );
|
|
my @presenceDevices =
|
|
split( ',', AttrVal( $name, $prefix . "presenceDevices", "" ) );
|
|
|
|
foreach my $event ( @{$events} ) {
|
|
next unless ( defined($event) );
|
|
my $found = 0;
|
|
|
|
# process wakeup devices
|
|
if (@registeredWakeupdevs) {
|
|
|
|
# if this is an event of a registered wakeup device
|
|
if ( grep { m/^$devName$/ } @registeredWakeupdevs ) {
|
|
RESIDENTStk_wakeupSet( $devName, $event );
|
|
next;
|
|
}
|
|
|
|
# process sub-child events: *_wakeupDevice
|
|
foreach my $wakeupDev (@registeredWakeupdevs) {
|
|
|
|
# if this is an event of a registered sub dummy device
|
|
# of one of our wakeup devices
|
|
if ( AttrVal( $wakeupDev, "wakeupResetSwitcher", "" ) eq
|
|
$devName
|
|
&& IsDevice( $devName, "dummy" ) )
|
|
{
|
|
RESIDENTStk_wakeupSet( $wakeupDev, $event )
|
|
unless ( $event =~ /^(?:state:\s*)?off$/i );
|
|
$found = 1;
|
|
last;
|
|
}
|
|
}
|
|
next if ($found);
|
|
}
|
|
|
|
# process PRESENCE
|
|
if ( @presenceDevices
|
|
&& ( grep { /^$devName(:[A-Za-z\d_\.\-\/]+)?$/ } @presenceDevices )
|
|
&& $event =~ /^(?:([A-Za-z\d_\.\-\/]+): )?(.+)$/ )
|
|
{
|
|
my $reading = $1;
|
|
my $value = $2;
|
|
|
|
# early exit if unexpected event value
|
|
next
|
|
unless ( $value =~
|
|
m/^0|false|absent|disappeared|unavailable|unreachable|disconnected|1|true|present|appeared|available|reachable|connected$/i
|
|
);
|
|
|
|
my $counter = {
|
|
absent => 0,
|
|
present => 0,
|
|
};
|
|
|
|
for (@presenceDevices) {
|
|
my $r = "presence";
|
|
my $d = $_;
|
|
if ( $d =~ m/^([a-zA-Z\d._]+):(?:([A-Za-z\d_\.\-\/]*))?$/ ) {
|
|
$d = $1;
|
|
$r = $2 if ( $2 && $2 ne "" );
|
|
}
|
|
|
|
my $presenceState =
|
|
ReadingsVal( $d, $r, ReadingsVal( $d, "state", "" ) );
|
|
|
|
# ignore device if it has unexpected state
|
|
next
|
|
unless ( $presenceState =~
|
|
m/^(0|false|absent|disappeared|unavailable|unreachable|disconnected)|(1|true|present|appeared|available|reachable|connected|)$/i
|
|
);
|
|
|
|
$counter->{absent}++ if ( defined($1) );
|
|
$counter->{present}++ if ( defined($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 "";
|
|
}
|
|
|
|
# module Fn ####################################################################
|
|
sub RESIDENTStk_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",
|
|
( $prefix eq "rr_" ? 36 : 16 )
|
|
);
|
|
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'";
|
|
RESIDENTStk_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_AutoGone", $hash, 1 );
|
|
}
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub RESIDENTStk_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 )
|
|
? UConv::s2hms($durPresence)
|
|
: "00:00:00";
|
|
my $durPresence_cr =
|
|
( $durPresence > 60 ) ? int( $durPresence / 60 + 0.5 ) : 0;
|
|
my $durAbsence_hr =
|
|
( $durAbsence > 0 ) ? UConv::s2hms($durAbsence) : "00:00:00";
|
|
my $durAbsence_cr =
|
|
( $durAbsence > 60 ) ? int( $durAbsence / 60 + 0.5 ) : 0;
|
|
my $durSleep_hr = ( $durSleep > 0 ) ? UConv::s2hms($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_DurationTimer", $hash, 1 );
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub RESIDENTStk_SetLocation(@) {
|
|
my (
|
|
$name, $location, $trigger, $id,
|
|
$time, $lat, $long, $address,
|
|
$device, $radius, $posLat, $posLong,
|
|
$posAddress, $posBeaconUUID, $posDistHome, $posDistLoc,
|
|
$motion, $wifiSSID, $wifiBSSID
|
|
) = @_;
|
|
my $hash = $defs{$name};
|
|
my $TYPE = GetType($name);
|
|
my $prefix = RESIDENTStk_GetPrefixFromType($name);
|
|
my $state = ReadingsVal( $name, "state", "initialized" );
|
|
my $presence = ReadingsVal( $name, "presence", "present" );
|
|
my $currLocation = ReadingsVal( $name, "location", "-" );
|
|
my $currWayhome = ReadingsVal( $name, "wayhome", "0" );
|
|
my $currLat = ReadingsVal( $name, "locationLat", "-" );
|
|
my $currLong = ReadingsVal( $name, "locationLong", "-" );
|
|
my $currRadius = ReadingsVal( $name, "locationRadius", "" );
|
|
my $currAddr = ReadingsVal( $name, "locationAddr", "" );
|
|
my $currPosLat = ReadingsVal( $name, "positionLat", "" );
|
|
my $currPosLong = ReadingsVal( $name, "positionLong", "" );
|
|
my $currPosAddr = ReadingsVal( $name, "positionAddr", "" );
|
|
my $currPosBeaconUUID = ReadingsVal( $name, "positionBeaconUUID", "" );
|
|
my $currPosDistHome = ReadingsVal( $name, "positionDistHome", "" );
|
|
my $currPosDistLoc = ReadingsVal( $name, "positionDistLocation", "" );
|
|
my $currPosMotion = ReadingsVal( $name, "positionMotion", "" );
|
|
my $currPosSSID = ReadingsVal( $name, "positionSSID", "" );
|
|
my $currPosBSSID = ReadingsVal( $name, "positionBSSID", "" );
|
|
$id = "-" if ( !$id || $id eq "" );
|
|
$lat = "-" if ( !$lat || $lat eq "" );
|
|
$long = "-" if ( !$long || $long eq "" );
|
|
$address = "" if ( !$address );
|
|
$time = "" if ( !$time );
|
|
$device = "" if ( !$device );
|
|
$posLat = "" if ( !$posLat || $posLat eq "-" );
|
|
$posLong = "" if ( !$posLong || $posLong eq "-" );
|
|
$posAddress = "-" if ( !$posAddress || $posAddress eq "" );
|
|
$posBeaconUUID = "" if ( !$posBeaconUUID || $posBeaconUUID eq "-" );
|
|
|
|
Log3 $name, 5,
|
|
"$TYPE $name: received location information: id=$id name=$location trig=$trigger date=$time lat=$lat long=$long posLat=$posLat posLong=$posLong address=$address device=$device";
|
|
|
|
my $searchstring;
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
# read attributes
|
|
my @location_home =
|
|
split( ' ', AttrVal( $name, $prefix . "locationHome", "home" ) );
|
|
my @location_underway =
|
|
split( ' ', AttrVal( $name, $prefix . "locationUnderway", "underway" ) );
|
|
my @location_wayhome =
|
|
split( ' ', AttrVal( $name, $prefix . "locationWayhome", "wayhome" ) );
|
|
|
|
$searchstring = quotemeta($location);
|
|
|
|
# update locationPresence
|
|
# if ( $posBeaconUUID eq "" ) {
|
|
readingsBulkUpdate( $hash, "locationPresence", "present" )
|
|
if ( $trigger == 1 );
|
|
readingsBulkUpdate( $hash, "locationPresence", "absent" )
|
|
if ( $trigger == 0 );
|
|
|
|
# }
|
|
|
|
# # update positionPresence
|
|
# readingsBulkUpdate( $hash, "positionPresence", "present" )
|
|
# if ( $trigger == 1 );
|
|
# readingsBulkUpdate( $hash, "positionPresence", "absent" )
|
|
# if ( $trigger == 0 );
|
|
|
|
# travelled distance for location
|
|
my $locTravDist = "";
|
|
if ( $lat ne "" && $long ne "" ) {
|
|
my $locLatVal = ReadingsVal( $name, "locationLat", "-" );
|
|
$locLatVal = ReadingsVal( $name, "lastLocationLat", "-" )
|
|
if ( $locLatVal eq "-" );
|
|
my $locLongVal = ReadingsVal( $name, "locationLong", "-" );
|
|
$locLongVal = ReadingsVal( $name, "lastLocationLong", "-" )
|
|
if ( $locLongVal eq "-" );
|
|
|
|
if ( $locLatVal ne "-" && $locLongVal ne "-" ) {
|
|
$locTravDist =
|
|
UConv::distance( $lat, $long, $locLatVal, $locLongVal, 2 );
|
|
}
|
|
}
|
|
|
|
# travelled distance for position
|
|
my $posTravDist = "";
|
|
if ( $posLat ne "" && $posLong ne "" ) {
|
|
my $currPosLatVal = ReadingsVal( $name, "positionLat", "" );
|
|
my $currPosLongVal = ReadingsVal( $name, "positionLong", "" );
|
|
|
|
if ( $currPosLatVal ne "" && $currPosLongVal ne "" ) {
|
|
$posTravDist = UConv::distance( $posLat, $posLong, $currPosLatVal,
|
|
$currPosLongVal, 2 );
|
|
}
|
|
}
|
|
|
|
# backup last known position
|
|
foreach (
|
|
'positionLat', 'positionLong',
|
|
'positionAddr', 'positionBeaconUUID',
|
|
'positionDistHome', 'positionDistLocation',
|
|
'positionMotion', 'positionSSID',
|
|
'positionBSSID', 'positionTravDistance',
|
|
'locationTravDistance'
|
|
)
|
|
{
|
|
my $currReading = $_;
|
|
my $lastReading = "last" . ucfirst($_);
|
|
my $currVal = ReadingsVal( $name, $currReading, undef );
|
|
readingsBulkUpdate( $hash, $lastReading, $currVal )
|
|
if ( defined($currVal) );
|
|
}
|
|
|
|
# update position based readings
|
|
readingsBulkUpdate( $hash, "positionLat", $posLat );
|
|
readingsBulkUpdate( $hash, "positionLong", $posLong );
|
|
readingsBulkUpdate( $hash, "positionAddr", $posAddress );
|
|
readingsBulkUpdate( $hash, "positionBeaconUUID", $posBeaconUUID );
|
|
readingsBulkUpdate( $hash, "positionDistHome", $posDistHome );
|
|
readingsBulkUpdate( $hash, "positionDistLocation", $posDistLoc );
|
|
readingsBulkUpdate( $hash, "positionMotion", $motion );
|
|
readingsBulkUpdate( $hash, "positionSSID", $wifiSSID );
|
|
readingsBulkUpdate( $hash, "positionBSSID", $wifiBSSID );
|
|
readingsBulkUpdate( $hash, "positionTravDistance", $posTravDist );
|
|
readingsBulkUpdate( $hash, "locationTravDistance", $locTravDist );
|
|
|
|
# check for implicit state change
|
|
#
|
|
my $stateChange = 0;
|
|
|
|
# home
|
|
if ( $location eq "home" || grep( m/^$searchstring$/, @location_home ) ) {
|
|
Log3 $name, 5, "$TYPE $name: received signal from home location";
|
|
|
|
# home
|
|
if ( $state ne "home"
|
|
&& $state ne "gotosleep"
|
|
&& $state ne "asleep"
|
|
&& $state ne "awoken"
|
|
&& $trigger eq "1" )
|
|
{
|
|
$stateChange = 1;
|
|
}
|
|
|
|
# absent
|
|
elsif ($state ne "gone"
|
|
&& $state ne "none"
|
|
&& $state ne "absent"
|
|
&& $trigger eq "0" )
|
|
{
|
|
$stateChange = 2;
|
|
}
|
|
|
|
}
|
|
|
|
# underway
|
|
elsif ($location eq "underway"
|
|
|| $location eq "wayhome"
|
|
|| grep( m/^$searchstring$/, @location_underway )
|
|
|| grep( m/^$searchstring$/, @location_wayhome ) )
|
|
{
|
|
Log3 $name, 5, "$TYPE $name: received signal from underway location";
|
|
|
|
# absent
|
|
$stateChange = 2
|
|
if ( $state ne "gone"
|
|
&& $state ne "none"
|
|
&& $state ne "absent" );
|
|
}
|
|
|
|
# wayhome
|
|
if (
|
|
$location eq "wayhome"
|
|
|| ( grep( m/^$searchstring$/, @location_wayhome )
|
|
&& $trigger eq "0" )
|
|
)
|
|
{
|
|
Log3 $name, 5, "$TYPE $name: wayhome signal received";
|
|
|
|
# wayhome=true
|
|
if (
|
|
(
|
|
( $location eq "wayhome" && $trigger eq "1" )
|
|
|| ( $location ne "wayhome" && $trigger eq "0" )
|
|
)
|
|
&& ReadingsVal( $name, "wayhome", "0" ) ne "1"
|
|
)
|
|
{
|
|
Log3 $name, 3, "$TYPE $name: on way back home from $location";
|
|
readingsBulkUpdate( $hash, "wayhome", "1" );
|
|
}
|
|
|
|
# wayhome=false
|
|
elsif ($location eq "wayhome"
|
|
&& $trigger eq "0"
|
|
&& ReadingsVal( $name, "wayhome", "0" ) ne "0" )
|
|
{
|
|
Log3 $name, 3,
|
|
"$TYPE $name: seems not to be on way back home anymore";
|
|
readingsBulkUpdate( $hash, "wayhome", "0" );
|
|
}
|
|
|
|
}
|
|
|
|
# activate wayhome tracing when reaching another location while wayhome=1
|
|
elsif ( $stateChange == 0 && $trigger == 1 && $currWayhome == 1 ) {
|
|
Log3 $name, 3,
|
|
"$TYPE $name: seems to stay at $location before coming home";
|
|
readingsBulkUpdate( $hash, "wayhome", "2" );
|
|
}
|
|
|
|
# revert wayhome during active wayhome tracing
|
|
elsif ( $stateChange == 0 && $trigger == 0 && $currWayhome == 2 ) {
|
|
Log3 $name, 3, "$TYPE $name: finally on way back home from $location";
|
|
readingsBulkUpdate( $hash, "wayhome", "1" );
|
|
}
|
|
|
|
my $currLongDiff = 0;
|
|
my $currLatDiff = 0;
|
|
$currLongDiff =
|
|
maxNum( ReadingsVal( $name, "lastLocationLong", 0 ), $currLong ) -
|
|
minNum( ReadingsVal( $name, "lastLocationLong", 0 ), $currLong )
|
|
if ( $currLong ne "-" );
|
|
$currLatDiff =
|
|
maxNum( ReadingsVal( $name, "lastLocationLat", 0 ), $currLat ) -
|
|
minNum( ReadingsVal( $name, "lastLocationLat", 0 ), $currLat )
|
|
if ( $currLat ne "-" );
|
|
|
|
if (
|
|
$trigger == 1
|
|
&& ( $stateChange > 0
|
|
|| ReadingsVal( $name, "lastLocation", "-" ) ne $currLocation
|
|
|| $currLongDiff > 0.00002
|
|
|| $currLatDiff > 0.00002 )
|
|
)
|
|
{
|
|
Log3 $name, 5, "$TYPE $name: archiving last known location";
|
|
readingsBulkUpdate( $hash, "lastLocationLat", $currLat );
|
|
readingsBulkUpdate( $hash, "lastLocationLong", $currLong );
|
|
readingsBulkUpdate( $hash, "lastLocationRadius", $currRadius );
|
|
readingsBulkUpdate( $hash, "lastLocationAddr", $currAddr )
|
|
if ( $currAddr ne "" );
|
|
readingsBulkUpdate( $hash, "lastLocation", $currLocation );
|
|
}
|
|
|
|
readingsBulkUpdate( $hash, "locationLat", $lat );
|
|
readingsBulkUpdate( $hash, "locationLong", $long );
|
|
readingsBulkUpdate( $hash, "locationRadius", $radius );
|
|
|
|
if ( $address ne "" ) {
|
|
readingsBulkUpdate( $hash, "locationAddr", $address );
|
|
}
|
|
elsif ( $currAddr ne "" ) {
|
|
readingsBulkUpdate( $hash, "locationAddr", "-" );
|
|
}
|
|
|
|
readingsBulkUpdate( $hash, "location", $location );
|
|
|
|
readingsEndUpdate( $hash, 1 );
|
|
|
|
# trigger state change
|
|
if ( $stateChange > 0 ) {
|
|
Log3 $name, 4,
|
|
"$TYPE $name: implicit state change caused by location " . $location;
|
|
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "state", "home" )
|
|
if $stateChange == 1;
|
|
RESIDENTStk_Set( $hash, $name, "silentSet", "state", "absent" )
|
|
if $stateChange == 2;
|
|
}
|
|
|
|
}
|
|
|
|
sub RESIDENTStk_wakeupSet($$) {
|
|
my ( $NAME, $n ) = @_;
|
|
my ( $a, $h ) = parseParams($n);
|
|
my $cmd = shift @$a;
|
|
my $VALUE = join( " ", @$a );
|
|
|
|
$cmd =~ s/^state:\s*(.*)$/$1/;
|
|
return if ( $cmd =~ /^[A-Za-z]+:/ );
|
|
|
|
# filter non-registered events
|
|
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|wakeupOffset)$/i
|
|
)
|
|
{
|
|
Log3 $NAME, 6,
|
|
"RESIDENTStk $NAME: "
|
|
. "received unspecified event '$cmd' - nothing to do";
|
|
return;
|
|
}
|
|
|
|
my $wakeupMacro = AttrVal( $NAME, "wakeupMacro", 0 );
|
|
my $wakeupDefaultTime =
|
|
ReadingsVal( $NAME, "wakeupDefaultTime",
|
|
AttrVal( $NAME, "wakeupDefaultTime", 0 ) );
|
|
my $wakeupAtdevice = AttrVal( $NAME, "wakeupAtdevice", 0 );
|
|
my $wakeupUserdevice = AttrVal( $NAME, "wakeupUserdevice", 0 );
|
|
my $wakeupDays =
|
|
ReadingsVal( $NAME, "wakeupDays", AttrVal( $NAME, "wakeupDays", "" ) );
|
|
my $wakeupHolidays =
|
|
ReadingsVal( $NAME, "wakeupHolidays",
|
|
AttrVal( $NAME, "wakeupHolidays", "" ) );
|
|
my $wakeupResetdays =
|
|
ReadingsVal( $NAME, "wakeupResetdays",
|
|
AttrVal( $NAME, "wakeupResetdays", "" ) );
|
|
my $wakeupOffset =
|
|
ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
|
|
my $wakeupEnforced =
|
|
ReadingsVal( $NAME, "wakeupEnforced",
|
|
AttrVal( $NAME, "wakeupEnforced", 0 ) );
|
|
my $wakeupResetSwitcher = AttrVal( $NAME, "wakeupResetSwitcher", 0 );
|
|
my $holidayDevices = AttrVal( "global", "holiday2we", 0 );
|
|
my $room = AttrVal( $NAME, "room", 0 );
|
|
my $userattr = AttrVal( $NAME, "userattr", 0 );
|
|
my $lastRun = ReadingsVal( $NAME, "lastRun", "07:00" );
|
|
my $nextRun = ReadingsVal( $NAME, "nextRun", "07:00" );
|
|
my $running = ReadingsVal( $NAME, "running", 0 );
|
|
my $wakeupUserdeviceState = ReadingsVal( $wakeupUserdevice, "state", 0 );
|
|
my $atName = "at_" . $NAME;
|
|
my $wdNameGotosleep = "wd_" . $wakeupUserdevice . "_gotosleep";
|
|
my $wdNameAsleep = "wd_" . $wakeupUserdevice . "_asleep";
|
|
my $wdNameAwoken = "wd_" . $wakeupUserdevice . "_awoken";
|
|
my $macroName = "Macro_" . $NAME;
|
|
my $macroNameGotosleep = "Macro_" . $wakeupUserdevice . "_gotosleep";
|
|
my $macroNameAsleep = "Macro_" . $wakeupUserdevice . "_asleep";
|
|
my $macroNameAwoken = "Macro_" . $wakeupUserdevice . "_awoken";
|
|
my $TYPE = GetType($wakeupUserdevice);
|
|
my $prefix = RESIDENTStk_GetPrefixFromType($wakeupUserdevice);
|
|
|
|
my $wakeupUserdeviceRealname = "Bewohner";
|
|
|
|
if ( $TYPE eq "RESIDENTS" ) {
|
|
$wakeupUserdeviceRealname = AttrVal(
|
|
AttrVal( $NAME, "wakeupUserdevice", "" ),
|
|
AttrVal(
|
|
AttrVal( $NAME, "wakeupUserdevice", "" ),
|
|
$prefix . "realname", "alias"
|
|
),
|
|
$wakeupUserdeviceRealname
|
|
);
|
|
}
|
|
else {
|
|
$wakeupUserdeviceRealname = AttrVal(
|
|
AttrVal( $NAME, "wakeupUserdevice", "" ),
|
|
AttrVal(
|
|
AttrVal( $NAME, "wakeupUserdevice", "" ),
|
|
$prefix . "realname", "group"
|
|
),
|
|
$wakeupUserdeviceRealname
|
|
);
|
|
}
|
|
|
|
# check for required userattr attribute
|
|
my $userattributes =
|
|
"wakeupOffset:slider,0,1,120 wakeupDefaultTime:OFF,00:00,00:15,00:30,00:45,01:00,01:15,01:30,01:45,02:00,02:15,02:30,02:45,03:00,03:15,03:30,03:45,04:00,04:15,04:30,04:45,05:00,05:15,05:30,05:45,06:00,06:15,06:30,06:45,07:00,07:15,07:30,07:45,08:00,08:15,08:30,08:45,09:00,09:15,09:30,09:45,10:00,10:15,10:30,10:45,11:00,11:15,11:30,11:45,12:00,12:15,12:30,12:45,13:00,13:15,13:30,13:45,14:00,14:15,14:30,14:45,15:00,15:15,15:30,15:45,16:00,16:15,16:30,16:45,17:00,17:15,17:30,17:45,18:00,18:15,18:30,18:45,19:00,19:15,19:30,19:45,20:00,20:15,20:30,20:45,21:00,21:15,21:30,21:45,22:00,22:15,22:30,22:45,23:00,23:15,23:30,23:45 wakeupMacro wakeupUserdevice wakeupAtdevice wakeupResetSwitcher wakeupResetdays:multiple-strict,0,1,2,3,4,5,6 wakeupDays:multiple-strict,0,1,2,3,4,5,6 wakeupHolidays:andHoliday,orHoliday,andNoHoliday,orNoHoliday wakeupEnforced:0,1,2,3 wakeupWaitPeriod:slider,0,1,360";
|
|
if ( !$userattr || $userattr ne $userattributes ) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "adjusting dummy device for required attribute userattr";
|
|
fhem "attr $NAME userattr $userattributes";
|
|
}
|
|
|
|
# check for required userdevice attribute
|
|
if ( !$wakeupUserdevice ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "WARNING - set attribute wakeupUserdevice before running wakeup function!";
|
|
}
|
|
elsif ( !IsDevice($wakeupUserdevice) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "WARNING - user device $wakeupUserdevice does not exist!";
|
|
}
|
|
elsif ( !IsDevice( $wakeupUserdevice, "RESIDENTS|ROOMMATE|GUEST|PET" ) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "WARNING - defined user device '$wakeupUserdevice' is not a RESIDENTS, ROOMMATE, GUEST or PET device!";
|
|
}
|
|
|
|
# check for required wakeupMacro attribute
|
|
if ( !$wakeupMacro ) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "adjusting dummy device for required attribute wakeupMacro";
|
|
fhem "attr $NAME wakeupMacro $macroName";
|
|
$wakeupMacro = $macroName;
|
|
}
|
|
if ( !IsDevice($wakeupMacro) ) {
|
|
my $wakeUpMacroTemplate = "{\
|
|
##=============================================================================\
|
|
## This is an example wake-up program running within a period of 30 minutes:\
|
|
## - drive shutters upwards slowly\
|
|
## - light up a HUE bulb from 2000K to 5600K\
|
|
## - have some voice notifications via SONOS\
|
|
## - have some wake-up chill music via SONOS during program run\
|
|
##\
|
|
## Actual FHEM commands are commented out by default as they would need\
|
|
## to be adapted to your configuration.\
|
|
##\
|
|
## Available wake-up variables:\
|
|
## 1. \$EVTPART0 -> start or stop\
|
|
## 2. \$EVTPART1 -> target wake-up time\
|
|
## 3. \$EVTPART2 -> wake-up begin time considering wakeupOffset attribute\
|
|
## 4. \$EVTPART3 -> enforced wakeup yes=1,no=0 from wakeupEnforced attribute\
|
|
## 5. \$EVTPART4 -> device name of the user which called this macro\
|
|
## 6. \$EVTPART5 -> current state of user\
|
|
##=============================================================================\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## DELETE TEMP. AT-COMMANDS POTENTIALLY CREATED EARLIER BY THIS SCRIPT\
|
|
## Executed for start to cleanup in case this wake-up automation is re-started.\
|
|
## Executed for stop to cleanup in case the user ends this automation earlier.\
|
|
##\
|
|
fhem \"delete atTmp_.*_\".\$NAME.\":FILTER=TYPE=at\";;\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## BEGIN WAKE-UP PROGRAM\
|
|
## Run first automation commands and create temp. at-devices for lagging actions.\
|
|
##\
|
|
if (\$EVTPART0 eq \"start\") {\
|
|
Log3 \$NAME, 3, \"\$NAME: Wake-up program started for \$EVTPART4 with target time \$EVTPART1. Current state: \$EVTPART5\";;\
|
|
\
|
|
# fhem \"set BR_FloorLamp:FILTER=onoff=0 pct 1 : ct 2000 : transitiontime 0;; set BR_FloorLamp:FILTER=pct=1 pct 90 : ct 5600 : transitiontime 17700\";;\
|
|
\
|
|
# fhem \"define atTmp_1_\$NAME at +00:10:00 set BR_Shutter:FILTER=pct<20 pct 20\";;\
|
|
# fhem \"define atTmp_2_\$NAME at +00:20:00 set BR_Shutter:FILTER=pct<40 pct 40\";;\
|
|
# fhem \"define atTmp_4_\$NAME at +00:30:00 msg audio \\\@Sonos_Bedroom |Hint| Es ist \".\$EVTPART1.\" Uhr, Zeit zum aufstehen!;;;; set BR_FloorLamp:FILTER=pct<100 pct 100 60;;;; sleep 10;;;; set BR_Shutter:FILTER=pct<60 pct 60;;;; set Sonos_Bedroom:FILTER=Volume<10 Volume 10 10\";;\
|
|
\
|
|
# if wake-up should be enforced\
|
|
if (\$EVTPART3) {\
|
|
Log3 \$NAME, 3, \"\$NAME: planning enforced wake-up\";;\
|
|
# fhem \"define atTmp_3_\$NAME at +00:25:00 set Sonos_Bedroom:FILTER=Volume>4 Volume 4;;;; sleep 0.5;;;; set Sonos_Bedroom:FILTER=Shuffle=0 Shuffle 1;;;; sleep 0.5;;;; set Sonos_Bedroom StartFavourite Morning%20Sounds\";;\
|
|
# fhem \"define atTmp_4_\$NAME at +00:26:00 set Sonos_Bedroom:FILTER=Volume<5 Volume 5\";;\
|
|
# fhem \"define atTmp_5_\$NAME at +00:27:00 set Sonos_Bedroom:FILTER=Volume<6 Volume 6\";;\
|
|
# fhem \"define atTmp_6_\$NAME at +00:28:00 set Sonos_Bedroom:FILTER=Volume<7 Volume 7\";;\
|
|
# fhem \"define atTmp_7_\$NAME at +00:29:00 set Sonos_Bedroom:FILTER=Volume<8 Volume 8\";;\
|
|
}\
|
|
}\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## END WAKE-UP PROGRAM (OPTIONAL)\
|
|
## Put some post wake-up tasks here like reminders after the actual wake-up period.\
|
|
##\
|
|
## Note: Will only be run when program ends normally after minutes specified in wakeupOffset.\
|
|
## If stop was user-forced by sending explicit set-command 'stop', this is not executed\
|
|
## assuming the user does not want any further automation activities.\
|
|
##\
|
|
if (\$EVTPART0 eq \"stop\") {\
|
|
Log3 \$NAME, 3, \"\$NAME: Wake-up program ended for \$EVTPART4 with target time \$EVTPART1. Current state: \$EVTPART5\";;\
|
|
\
|
|
# if wake-up should be enforced, auto-change user state from 'asleep' to 'awoken'\
|
|
# after a small additional nap to kick you out of bed if user did not confirm to be awake :-)\
|
|
# An additional notify for user state 'awoken' may take further actions\
|
|
# and change to state 'home' afterwards.\
|
|
if (\$EVTPART3) {\
|
|
fhem \"define atTmp_9_\$NAME at +00:05:00 set \$EVTPART4:FILTER=state=asleep awoken\";;\
|
|
\
|
|
# Without enforced wake-up, be jentle and just set user state to 'home' after some\
|
|
# additional long nap time\
|
|
} else {\
|
|
fhem \"define atTmp_9_\$NAME at +01:30:00 set \$EVTPART4:FILTER=state=asleep home\";;\
|
|
}\
|
|
}\
|
|
\
|
|
}\
|
|
";
|
|
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new notify macro device $wakeupMacro created";
|
|
fhem "define $wakeupMacro notify $wakeupMacro $wakeUpMacroTemplate";
|
|
fhem
|
|
"attr $wakeupMacro comment Macro auto-created by RESIDENTS Toolkit";
|
|
fhem "attr $wakeupMacro room $room"
|
|
if ($room);
|
|
}
|
|
elsif ( !IsDevice( $wakeupMacro, "notify" ) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "WARNING - defined macro device '$wakeupMacro' is not a notify device!";
|
|
}
|
|
|
|
# check for required wakeupAtdevice attribute
|
|
if ( !$wakeupAtdevice ) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "adjusting dummy device for required attribute wakeupAtdevice";
|
|
fhem "attr $NAME wakeupAtdevice $atName";
|
|
$wakeupAtdevice = $atName;
|
|
}
|
|
if ( !IsDevice($wakeupAtdevice) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: " . "new at-device $wakeupAtdevice created";
|
|
fhem "define $wakeupAtdevice "
|
|
. "at *{RESIDENTStk_wakeupGetBegin(\"$NAME\",\"$wakeupAtdevice\")} set $NAME trigger";
|
|
fhem "attr $wakeupAtdevice "
|
|
. "comment Auto-created by RESIDENTS Toolkit: trigger wake-up timer at specific time";
|
|
fhem "attr $wakeupAtdevice computeAfterInit 1";
|
|
fhem "attr $wakeupAtdevice room $room"
|
|
if ($room);
|
|
|
|
########
|
|
# (re)create other notify and watchdog templates
|
|
# for ROOMMATE, GUEST or PET devices
|
|
|
|
# macro: gotosleep
|
|
if ( !IsDevice( $wakeupUserdevice, "RESIDENTS" )
|
|
&& !IsDevice($macroNameGotosleep) )
|
|
{
|
|
my $templateGotosleep = "{\
|
|
##=============================================================================\
|
|
## This is an example macro when gettin' ready for bed.\
|
|
##\
|
|
## Actual FHEM commands are commented out by default as they would need\
|
|
## to be adapted to your configuration.\
|
|
##=============================================================================\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## LIGHT SCENE\
|
|
##\
|
|
\
|
|
## Dim up floor light\
|
|
#fhem \"set FL_Light:FILTER=pct=0 pct 20\";;\
|
|
\
|
|
## Dim down bright ceilling light in bedroom\
|
|
#fhem \"set BR_Light:FILTER=pct!=0 pct 0 5\";;\
|
|
\
|
|
## Dim up HUE floor lamp with very low color temperature\
|
|
#fhem \"set BR_FloorLamp ct 2000 : pct 80 : transitiontime 30\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## ENVIRONMENT SCENE\
|
|
##\
|
|
\
|
|
## Turn down shutter to 28%\
|
|
#fhem \"set BR_Shutter:FILTER=pct>28 pct 28\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## PLAY CHILLOUT MUSIC\
|
|
## via SONOS at Bedroom and Bathroom\
|
|
##\
|
|
\
|
|
## Stop playback bedroom's Sonos device might be involved in\
|
|
#fhem \"set Sonos_Bedroom:transportState=PLAYING stop;;\";;\
|
|
\
|
|
## Make Bedroom's and Bathroom's Sonos devices a single device\
|
|
## and do not touch other Sonos devices (this is why we use RemoveMember!)\
|
|
#fhem \"sleep 0.5;; set Sonos_Bedroom RemoveMember Sonos_Bedroom\";;\
|
|
#fhem \"sleep 1.0;; set Sonos_Bathroom RemoveMember Sonos_Bathroom\";;\
|
|
\
|
|
## Group Bedroom's and Bathroom's Sonos devices with Bedroom as master\
|
|
#fhem \"sleep 2.0;; set Sonos_Bedroom AddMember Sonos_Bathroom;; set Sonos_Bedroom:FILTER=Shuffle!=1 Shuffle 1;; set Sonos_Bedroom:FILTER=Volume!=12,Sonos_Bathroom:FILTER=Volume!=12 Volume 12\";;\
|
|
\
|
|
## Start music from playlist\
|
|
#fhem \"sleep 3.0;; set Sonos_Bedroom StartFavourite Evening%%20Chill\";;\
|
|
\
|
|
return;;\
|
|
}";
|
|
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new notify macro device $macroNameGotosleep created";
|
|
fhem "define $macroNameGotosleep "
|
|
. "notify $macroNameGotosleep $templateGotosleep";
|
|
fhem "attr $macroNameGotosleep "
|
|
. "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run when gettin' ready for bed";
|
|
fhem "attr $macroNameGotosleep room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# wd: gotosleep
|
|
if ( !IsDevice($wdNameGotosleep) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new watchdog device $wdNameGotosleep created";
|
|
fhem "define $wdNameGotosleep "
|
|
. "watchdog $wakeupUserdevice:(gotosleep|bettfertig) 00:00:04 $wakeupUserdevice:(home|anwesend|zuhause|absent|abwesend|gone|verreist|asleep|schlaeft|schläft|awoken|aufgestanden) trigger $macroNameGotosleep";
|
|
fhem "attr $wdNameGotosleep autoRestart 1";
|
|
fhem "attr $wdNameGotosleep "
|
|
. "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state gotosleep";
|
|
fhem "attr $wdNameGotosleep room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# macro: asleep
|
|
if ( !IsDevice( $wakeupUserdevice, "RESIDENTS" )
|
|
&& !IsDevice($macroNameAsleep) )
|
|
{
|
|
my $templateAsleep = "{\
|
|
##=============================================================================\
|
|
## This is an example macro when jumpin' into bed and start to sleep.\
|
|
##\
|
|
## Actual FHEM commands are commented out by default as they would need\
|
|
## to be adapted to your configuration.\
|
|
##=============================================================================\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## LIGHT SCENE\
|
|
##\
|
|
\
|
|
## In 15 seconds, turn off all lights in Bedroom using a structure\
|
|
#fhem \"sleep 15;; set g_BR_Lights [FILTER=state!=off] off\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## ENVIRONMENT SCENE\
|
|
##\
|
|
\
|
|
## In 12 seconds, close shutter if window is closed\
|
|
#if (ReadingsVal(\"BR_Window\",\"state\",0) eq \"closed\") {\
|
|
# fhem \"sleep 12;; set BR_Shutter:FILTER=pct>0 close\";;\
|
|
\
|
|
## In 12 seconds, if window is not closed just make sure shutter is at least\
|
|
## at 28% to allow some ventilation\
|
|
#} else {\
|
|
# fhem \"sleep 12;; set BR_Shutter:FILTER=pct>28 pct 28\";;\
|
|
#}\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## PLAY WAKE-UP ANNOUNCEMENT\
|
|
## via SONOS at Bedroom and stop playback elsewhere\
|
|
##\
|
|
\
|
|
#my \$nextWakeup = ReadingsVal(\"$wakeupUserdevice\",\"nextWakeup\",\"none\");;
|
|
#my \$text = \"|Hint| $wakeupUserdeviceRealname, es ist kein Wecker gestellt. Du könntest verschlafen! Trotzdem eine gute Nacht.\";;
|
|
#if (\$nextWakeup ne \"OFF\") {
|
|
# \$text = \"|Hint| $wakeupUserdeviceRealname, dein Wecker ist auf \$nextWakeup Uhr gestellt. Gute Nacht und schlaf gut.\";;
|
|
#}
|
|
#if (\$nextWakeup ne \"none\") {
|
|
# fhem \"set Sonos_Bedroom RemoveMember Sonos_Bedroom;; sleep 0.5;; msg audio \\\@Sonos_Bedroom \$text\";;\
|
|
#}
|
|
\
|
|
return;;\
|
|
}";
|
|
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new notify macro device $macroNameAsleep created";
|
|
fhem
|
|
"define $macroNameAsleep notify $macroNameAsleep $templateAsleep";
|
|
fhem "attr $macroNameAsleep "
|
|
. "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run when jumpin' into bed and start to sleep";
|
|
fhem "attr $macroNameAsleep room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# wd: asleep
|
|
if ( !IsDevice($wdNameAsleep) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new watchdog device $wdNameAsleep created";
|
|
fhem "define $wdNameAsleep "
|
|
. "watchdog $wakeupUserdevice:(asleep|schlaeft|schläft) 00:00:04 $wakeupUserdevice:(home|anwesend|zuhause|absent|abwesend|gone|verreist|gotosleep|bettfertig|awoken|aufgestanden) trigger $macroNameAsleep";
|
|
fhem "attr $wdNameAsleep autoRestart 1";
|
|
fhem "attr $wdNameAsleep "
|
|
. "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state asleep";
|
|
fhem "attr $wdNameAsleep room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# macro: awoken
|
|
if ( !IsDevice( $wakeupUserdevice, "RESIDENTS" )
|
|
&& !IsDevice($macroNameAwoken) )
|
|
{
|
|
my $templateAwoken = "{\
|
|
##=============================================================================\
|
|
## This is an example macro after confirming to be awake.\
|
|
##\
|
|
## Actual FHEM commands are commented out by default as they would need\
|
|
## to be adapted to your configuration.\
|
|
##=============================================================================\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## LIGHT SCENE\
|
|
##\
|
|
\
|
|
## Dim up HUE floor lamp to maximum with cold color temperature\
|
|
#fhem \"set BR_FloorLamp:FILTER=pct<100 pct 100 : ct 6500 : transitiontime 30\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## ENVIRONMENT SCENE\
|
|
##\
|
|
\
|
|
## In 22 seconds, turn up shutter at least until 60%\
|
|
#fhem \"sleep 22;; set BR_Shutter:FILTER=pct<60 60\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## RAMP-UP ALL MORNING STUFF\
|
|
##\
|
|
\
|
|
## Play morning announcement via SONOS at Bedroom\
|
|
#fhem \"set Sonos_Bedroom Stop;; msg audio \\\@Sonos_Bedroom |Hint| Guten Morgen, $wakeupUserdeviceRealname.\";;\
|
|
\
|
|
## In 10 seconds, start webradio playback in Bedroom\
|
|
#fhem \"sleep 10;; set Sonos_Bedroom StartRadio /Charivari/;; sleep 2;; set Sonos_Bedroom Volume 15\";;\
|
|
\
|
|
## Make webradio stream available at Bathroom and\
|
|
## Kitchen 5 seonds after it started\
|
|
#fhem \"set Sonos_Bathroom,Sonos_Kitchen Volume 15;; sleep 15;; set Sonos_Bedroom AddMember Sonos_Bathroom;; set Sonos_Bedroom AddMember Sonos_Kitchen\";;\
|
|
\
|
|
## change user state to home after 60 seconds\
|
|
fhem \"sleep 60;; set $wakeupUserdevice:FILTER=state!=home home\";;\
|
|
\
|
|
return;;\
|
|
}";
|
|
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new notify macro device $macroNameAwoken created";
|
|
fhem
|
|
"define $macroNameAwoken notify $macroNameAwoken $templateAwoken";
|
|
fhem "attr $macroNameAwoken "
|
|
. "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run after confirming to be awake";
|
|
fhem "attr $macroNameAwoken room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# wd: awoken
|
|
if ( !IsDevice($wdNameAwoken) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new watchdog device $wdNameAwoken created";
|
|
fhem "define $wdNameAwoken "
|
|
. "watchdog $wakeupUserdevice:(awoken|aufgestanden) 00:00:04 $wakeupUserdevice:(home|anwesend|zuhause|absent|abwesend|gone|verreist|gotosleep|bettfertig|asleep|schlaeft|schläft) trigger $macroNameAwoken";
|
|
fhem "attr $wdNameAwoken autoRestart 1";
|
|
fhem "attr $wdNameAwoken "
|
|
. "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state awoken";
|
|
fhem "attr $wdNameAwoken room $room"
|
|
if ($room);
|
|
}
|
|
|
|
########
|
|
# (re)create other notify and watchdog templates
|
|
# for RESIDENT devices
|
|
#
|
|
|
|
my $RESIDENTGROUPS;
|
|
if ( IsDevice( $wakeupUserdevice, "RESIDENTS" ) ) {
|
|
$RESIDENTGROUPS = $wakeupUserdevice;
|
|
}
|
|
elsif ( IsDevice($wakeupUserdevice)
|
|
&& defined( $defs{$wakeupUserdevice}{RESIDENTGROUPS} ) )
|
|
{
|
|
$RESIDENTGROUPS = $defs{$wakeupUserdevice}{RESIDENTGROUPS};
|
|
}
|
|
|
|
for my $deviceName ( split /,/, $RESIDENTGROUPS ) {
|
|
my $macroRNameGotosleep = "Macro_" . $deviceName . "_gotosleep";
|
|
my $macroRNameAsleep = "Macro_" . $deviceName . "_asleep";
|
|
my $macroRNameAwoken = "Macro_" . $deviceName . "_awoken";
|
|
my $wdRNameGotosleep = "wd_" . $deviceName . "_gotosleep";
|
|
my $wdRNameAsleep = "wd_" . $deviceName . "_asleep";
|
|
my $wdRNameAwoken = "wd_" . $deviceName . "_awoken";
|
|
|
|
# macro: gotosleep
|
|
if ( !IsDevice($macroRNameGotosleep) ) {
|
|
my $templateGotosleep = "{\
|
|
##=============================================================================\
|
|
## This is an example macro when all residents are gettin' ready for bed.\
|
|
##\
|
|
## Actual FHEM commands are commented out by default as they would need\
|
|
## to be adapted to your configuration.\
|
|
##=============================================================================\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## HOUSE MODE\
|
|
## Enforce evening mode if we are still in day mode\
|
|
##\
|
|
\
|
|
#fhem \"set HouseMode:FILTER=state=day evening\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## LIGHT SCENE\
|
|
##\
|
|
\
|
|
## In 10 seconds, turn off lights in unused rooms using structures\
|
|
#fhem \"sleep 10;; set g_LR_Lights,g_KT_Lights [FILTER=state!=off] off\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## ENVIRONMENT SCENE\
|
|
##\
|
|
\
|
|
## Turn off all media devices in the Living Room\
|
|
#fhem \"set g_HSE_Media [FILTER=state!=off] off\";;\
|
|
\
|
|
return;;\
|
|
}";
|
|
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new notify macro device $macroRNameGotosleep created";
|
|
fhem "define $macroRNameGotosleep "
|
|
. "notify $macroRNameGotosleep $templateGotosleep";
|
|
fhem "attr $macroRNameGotosleep "
|
|
. "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run when all residents are gettin' ready for bed";
|
|
fhem "attr $macroRNameGotosleep room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# wd: gotosleep
|
|
if ( !IsDevice($wdRNameGotosleep) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new watchdog device $wdRNameGotosleep created";
|
|
fhem "define $wdRNameGotosleep "
|
|
. "watchdog $deviceName:(gotosleep|bettfertig) 00:00:03 $deviceName:(home|anwesend|zuhause|absent|abwesend|gone|verreist|asleep|schlaeft|schläft|awoken|aufgestanden) trigger $macroRNameGotosleep";
|
|
fhem "attr $wdRNameGotosleep autoRestart 1";
|
|
fhem "attr $wdRNameGotosleep "
|
|
. "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state gotosleep";
|
|
fhem "attr $wdRNameGotosleep room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# macro: asleep
|
|
if ( !IsDevice($macroRNameAsleep) ) {
|
|
my $templateAsleep = "{\
|
|
##=============================================================================\
|
|
## This is an example macro when all residents are in their beds.\
|
|
##\
|
|
## Actual FHEM commands are commented out by default as they would need\
|
|
## to be adapted to your configuration.\
|
|
##=============================================================================\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## HOUSE MODE\
|
|
## Enforce night mode if we are still in evening mode\
|
|
##\
|
|
\
|
|
#fhem \"set HouseMode:FILTER=state=evening night\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## LIGHT SCENE\
|
|
##\
|
|
\
|
|
## In 20 seconds, turn off all lights in the house using structures\
|
|
#fhem \"sleep 20;; set g_HSE_Lights [FILTER=state!=off] off\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## ENVIRONMENT SCENE\
|
|
##\
|
|
\
|
|
## Stop playback at SONOS devices in shared rooms, e.g. Bathroom\
|
|
#fhem \"set Sonos_Bathroom:FILTER=transportState=PLAYING Stop\";;\
|
|
\
|
|
return;;\
|
|
}";
|
|
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new notify macro device $macroRNameAsleep created";
|
|
fhem "define $macroRNameAsleep "
|
|
. "notify $macroRNameAsleep $templateAsleep";
|
|
fhem "attr $macroRNameAsleep "
|
|
. "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run when all residents are in their beds";
|
|
fhem "attr $macroRNameAsleep room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# wd: asleep
|
|
if ( !IsDevice($wdRNameAsleep) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new watchdog device $wdNameAsleep created";
|
|
fhem "define $wdRNameAsleep "
|
|
. "watchdog $deviceName:(asleep|schlaeft|schläft) 00:00:03 $deviceName:(home|anwesend|zuhause|absent|abwesend|gone|verreist|gotosleep|bettfertig|awoken|aufgestanden) trigger $macroRNameAsleep";
|
|
fhem "attr $wdRNameAsleep autoRestart 1";
|
|
fhem "attr $wdRNameAsleep "
|
|
. "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state asleep";
|
|
fhem "attr $wdRNameAsleep room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# macro: awoken
|
|
if ( !IsDevice($macroRNameAwoken) ) {
|
|
my $templateAwoken = "{\
|
|
##=============================================================================\
|
|
## This is an example macro when the first resident has confirmed to be awake\
|
|
##\
|
|
## Actual FHEM commands are commented out by default as they would need\
|
|
## to be adapted to your configuration.\
|
|
##=============================================================================\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## HOUSE MODE\
|
|
## Enforce morning mode if we are still in night mode\
|
|
##\
|
|
\
|
|
#fhem \"set HouseMode:FILTER=state=night morning\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## LIGHT SCENE\
|
|
##\
|
|
\
|
|
## Turn on lights in the Kitchen already but set a timer to turn it off again\
|
|
#fhem \"set KT_CounterLight on-for-timer 6300\";;\
|
|
\
|
|
\
|
|
##-----------------------------------------------------------------------------\
|
|
## PREPARATIONS\
|
|
##\
|
|
\
|
|
## In 90 minutes, switch House Mode to 'day' and\
|
|
## play voice announcement via SONOS\
|
|
#unless (IsDevice(\"atTmpHouseMode_day\")) {\
|
|
# fhem \"define atTmpHouseMode_day at +01:30:00 {if (ReadingsVal(\\\"HouseMode\\\", \\\"state\\\", 0) ne \\\"day\\\") {fhem \\\"msg audio \\\@Sonos_Kitchen Tagesmodus wird etabliert.;;;; sleep 10;;;; set HouseMode day\\\"}}\";;\
|
|
#}\
|
|
\
|
|
return;;\
|
|
}";
|
|
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new notify macro device $macroRNameAwoken created";
|
|
fhem "define $macroRNameAwoken "
|
|
. "notify $macroRNameAwoken $templateAwoken";
|
|
fhem "attr $macroRNameAwoken "
|
|
. "comment Auto-created by RESIDENTS Toolkit: FHEM commands to run after first resident confirmed to be awake";
|
|
fhem "attr $macroRNameAwoken room $room"
|
|
if ($room);
|
|
}
|
|
|
|
# wd: awoken
|
|
if ( !IsDevice($wdRNameAwoken) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "new watchdog device $wdNameAwoken created";
|
|
fhem "define $wdRNameAwoken "
|
|
. "watchdog $deviceName:(awoken|aufgestanden) 00:00:04 $deviceName:(home|anwesend|zuhause|absent|abwesend|gone|verreist|gotosleep|bettfertig|asleep|schlaeft|schläft) trigger $macroRNameAwoken";
|
|
fhem "attr $wdRNameAwoken autoRestart 1";
|
|
fhem "attr $wdRNameAwoken "
|
|
. "comment Auto-created by RESIDENTS Toolkit: trigger macro after going to state awoken";
|
|
fhem "attr $wdRNameAwoken room $room"
|
|
if ($room);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
elsif ( !IsDevice( $wakeupAtdevice, "at" ) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "WARNING - defined at-device '$wakeupAtdevice' is not an at-device!";
|
|
}
|
|
elsif ( AttrVal( $wakeupAtdevice, "computeAfterInit", 0 ) ne "1" ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "Correcting '$wakeupAtdevice' attribute computeAfterInit required for correct recalculation after reboot";
|
|
fhem "attr $wakeupAtdevice computeAfterInit 1";
|
|
}
|
|
|
|
# verify holiday2we attribute
|
|
if ( $wakeupHolidays ne "" ) {
|
|
if ( !$holidayDevices ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "ERROR - wakeupHolidays set in this alarm clock but global attribute holiday2we not set!";
|
|
return "ERROR: "
|
|
. "wakeupHolidays set in this alarm clock but global attribute holiday2we not set!";
|
|
}
|
|
else {
|
|
foreach my $holidayDevice ( split( ',', $holidayDevices ) ) {
|
|
if ( !IsDevice($holidayDevice) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "ERROR - global attribute holiday2we has reference to non-existing device $holidayDevice";
|
|
return "ERROR: "
|
|
. "global attribute holiday2we has reference to non-existing device $holidayDevice";
|
|
}
|
|
elsif ( !IsDevice( $holidayDevice, "holiday" ) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "ERROR - global attribute holiday2we seems to have invalid device reference - $holidayDevice is not of type 'holiday'";
|
|
return "ERROR: "
|
|
. "global attribute holiday2we seems to have invalid device reference - $holidayDevice is not of type 'holiday'";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# start
|
|
#
|
|
if ( $cmd eq "start" ) {
|
|
RESIDENTStk_wakeupRun( $NAME, 1 );
|
|
}
|
|
|
|
# trigger
|
|
#
|
|
elsif ( $cmd eq "trigger" ) {
|
|
RESIDENTStk_wakeupRun($NAME);
|
|
}
|
|
|
|
# stop | end
|
|
#
|
|
elsif ( $cmd eq "stop" || $cmd eq "end" ) {
|
|
if ($running) {
|
|
Log3 $NAME, 4, "RESIDENTStk $NAME: " . "stopping wake-up program";
|
|
readingsSingleUpdate( $defs{$NAME}, "running", "0", 1 );
|
|
}
|
|
|
|
fhem "set $NAME nextRun $nextRun";
|
|
return unless ($running);
|
|
|
|
# trigger macro again so it may clean up it's stuff.
|
|
# use $EVTPART1 to check
|
|
if ( !$wakeupMacro ) {
|
|
Log3 $NAME, 2,
|
|
"RESIDENTStk $NAME: " . "missing attribute wakeupMacro";
|
|
}
|
|
elsif ( !IsDevice($wakeupMacro) ) {
|
|
Log3 $NAME, 2,
|
|
"RESIDENTStk $NAME: "
|
|
. "notify macro $wakeupMacro not found - no wakeup actions defined!";
|
|
}
|
|
elsif ( !IsDevice( $wakeupMacro, "notify" ) ) {
|
|
Log3 $NAME, 2,
|
|
"RESIDENTStk $NAME: "
|
|
. "device $wakeupMacro is not of type notify";
|
|
}
|
|
else {
|
|
|
|
# conditional enforced wake-up:
|
|
# only if actual wake-up time is
|
|
# earlier than wakeupDefaultTime
|
|
if ( $wakeupEnforced == 3
|
|
&& $wakeupDefaultTime
|
|
&& UConv::hms2s($wakeupDefaultTime) > UConv::hms2s($lastRun) )
|
|
{
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "Enforcing wake-up because wake-up time is earlier than normal (wakeupDefaultTime=$wakeupDefaultTime > lastRun=$lastRun)";
|
|
$wakeupEnforced = 1;
|
|
}
|
|
|
|
# conditional enforced wake-up:
|
|
# only if actual wake-up time is not wakeupDefaultTime
|
|
elsif ($wakeupEnforced == 2
|
|
&& $wakeupDefaultTime
|
|
&& $wakeupDefaultTime ne $lastRun )
|
|
{
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)";
|
|
$wakeupEnforced = 1;
|
|
}
|
|
elsif ( $wakeupEnforced > 1 ) {
|
|
$wakeupEnforced = 0;
|
|
}
|
|
|
|
if ( $VALUE ne "" || $cmd eq "end" ) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "trigger $wakeupMacro stop $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
|
|
fhem "trigger $wakeupMacro "
|
|
. "stop $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
|
|
}
|
|
else {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "trigger $wakeupMacro forced-stop $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
|
|
fhem "trigger $wakeupMacro "
|
|
. "forced-stop $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
|
|
|
|
fhem "set $wakeupUserdevice:FILTER=state=asleep awoken";
|
|
}
|
|
|
|
my $wakeupStopAtdevice = $wakeupAtdevice . "_stop";
|
|
fhem "delete $wakeupStopAtdevice"
|
|
if ( IsDevice($wakeupStopAtdevice) );
|
|
}
|
|
|
|
readingsSingleUpdate( $defs{$wakeupUserdevice}, "wakeup", "0", 1 );
|
|
return;
|
|
}
|
|
|
|
# auto or reset
|
|
#
|
|
elsif ( $cmd eq "auto" || $cmd eq "reset" ) {
|
|
my $resetTime = ReadingsVal( $NAME, "lastRun", 0 );
|
|
if ($wakeupDefaultTime) {
|
|
$resetTime = $wakeupDefaultTime;
|
|
}
|
|
|
|
if ( $resetTime
|
|
&& !( $cmd eq "auto" && lc($resetTime) eq "off" ) )
|
|
{
|
|
fhem "set $NAME:FILTER=state!=$resetTime nextRun $resetTime";
|
|
}
|
|
elsif ( $cmd eq "reset" ) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "no default value specified in attribute wakeupDefaultTime, just keeping setting OFF";
|
|
fhem "set $NAME:FILTER=state!=OFF nextRun OFF";
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
# wakeup attributes
|
|
#
|
|
elsif ( $cmd =~
|
|
m/^(wakeupResetdays|wakeupDays|wakeupHolidays|wakeupEnforced|wakeupDefaultTime|wakeupOffset)$/
|
|
)
|
|
{
|
|
Log3 $NAME, 4, "RESIDENTStk $NAME: " . "setting $1 to '$VALUE'";
|
|
|
|
readingsBeginUpdate( $defs{$NAME} );
|
|
readingsBulkUpdate( $defs{$NAME}, $1, $VALUE );
|
|
readingsEndUpdate( $defs{$NAME}, 1 );
|
|
|
|
fhem "set $NAME nextRun $nextRun";
|
|
|
|
return;
|
|
}
|
|
|
|
# set new wakeup value
|
|
elsif ( IsDevice( $wakeupAtdevice, "at" )
|
|
&& $cmd =~
|
|
m/^(?:nextRun)?\s*(OFF|([\+\-])?(([0-9]{2}):([0-9]{2})|([1-9]+[0-9]*)))?$/i
|
|
)
|
|
{
|
|
$VALUE = $1 if ( $1 && $1 ne "" );
|
|
|
|
$VALUE =
|
|
RESIDENTStk_TimeSum( ReadingsVal( $NAME, "nextRun", 0 ), $VALUE )
|
|
if ($2);
|
|
|
|
# Update wakeuptimer device
|
|
#
|
|
readingsBeginUpdate( $defs{$NAME} );
|
|
if ( ReadingsVal( $NAME, "nextRun", 0 ) ne $VALUE ) {
|
|
Log3 $NAME, 4, "RESIDENTStk $NAME: " . "New wake-up time: $VALUE";
|
|
readingsBulkUpdate( $defs{$NAME}, "nextRun", $VALUE );
|
|
|
|
# Update at-device
|
|
fhem "set $wakeupAtdevice "
|
|
. "modifyTimeSpec {RESIDENTStk_wakeupGetBegin(\"$NAME\",\"$wakeupAtdevice\")}";
|
|
}
|
|
if ( ReadingsVal( $NAME, "state", 0 ) ne $VALUE
|
|
&& !$running )
|
|
{
|
|
readingsBulkUpdate( $defs{$NAME}, "state", $VALUE );
|
|
}
|
|
elsif ( ReadingsVal( $NAME, "state", 0 ) ne "running"
|
|
&& $running )
|
|
{
|
|
readingsBulkUpdate( $defs{$NAME}, "state", "running" );
|
|
}
|
|
readingsEndUpdate( $defs{$NAME}, 1 );
|
|
|
|
# Update user device
|
|
#
|
|
readingsBeginUpdate( $defs{$wakeupUserdevice} );
|
|
my ( $nextWakeupDev, $nextWakeup ) =
|
|
RESIDENTStk_wakeupGetNext($wakeupUserdevice);
|
|
if ( !$nextWakeupDev || !$nextWakeup ) {
|
|
$nextWakeupDev = "";
|
|
$nextWakeup = "OFF";
|
|
}
|
|
readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
|
|
"nextWakeupDev", $nextWakeupDev );
|
|
readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
|
|
"nextWakeup", $nextWakeup );
|
|
readingsEndUpdate( $defs{$wakeupUserdevice}, 1 );
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub RESIDENTStk_wakeupGetBegin($;$) {
|
|
my ( $NAME, $wakeupAtdevice ) = @_;
|
|
|
|
unless ( IsDevice($NAME) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "Run function RESIDENTStk_wakeupGetBegin() for non-existing device!";
|
|
return "$NAME: Non-existing device";
|
|
}
|
|
|
|
my $nextRun = ReadingsVal( $NAME, "nextRun", 0 );
|
|
my $wakeupDefaultTime =
|
|
ReadingsVal( $NAME, "wakeupDefaultTime",
|
|
AttrVal( $NAME, "wakeupDefaultTime", 0 ) );
|
|
my $wakeupOffset =
|
|
ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
|
|
my $wakeupInitTime = (
|
|
$wakeupDefaultTime && lc($wakeupDefaultTime) ne "off"
|
|
? $wakeupDefaultTime
|
|
: "05:00"
|
|
);
|
|
my $wakeupTime;
|
|
|
|
if ($wakeupAtdevice) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "Wakeuptime recalculation triggered by at-device $wakeupAtdevice";
|
|
}
|
|
|
|
# just give any valuable return to at-device
|
|
# if wakeuptimer device does not exit anymore
|
|
# and run self-destruction to clean up
|
|
if ( !IsDevice($NAME) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "this wake-up timer device does not exist anymore";
|
|
my $atName = "at_" . $NAME;
|
|
|
|
if ( IsDevice( $wakeupAtdevice, "at" ) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "Cleaning up at-device $wakeupAtdevice (self-destruction)";
|
|
fhem "sleep 1; delete $wakeupAtdevice";
|
|
}
|
|
elsif ( IsDevice( $atName, "at" ) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: " . "Cleaning up at-device $atName";
|
|
fhem "sleep 1; delete $atName";
|
|
}
|
|
else {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "Could not automatically clean up at-device, please perform manual cleanup.";
|
|
}
|
|
|
|
return $wakeupInitTime;
|
|
}
|
|
|
|
# use nextRun value if not OFF
|
|
if ( $nextRun && lc($nextRun) ne "off" ) {
|
|
$wakeupTime = $nextRun;
|
|
Log3 $NAME, 4, "RESIDENTStk $NAME: " . "wakeupGetBegin source: nextRun";
|
|
}
|
|
|
|
# use wakeupDefaultTime if present and not OFF
|
|
elsif ( $wakeupDefaultTime
|
|
&& lc($wakeupDefaultTime) ne "off" )
|
|
{
|
|
$wakeupTime = $wakeupDefaultTime;
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: " . "wakeupGetBegin source: wakeupDefaultTime";
|
|
}
|
|
|
|
# Use a default value to ensure auto-reset at least once a day
|
|
else {
|
|
$wakeupTime = $wakeupInitTime;
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: " . "wakeupGetBegin source: defaultValue";
|
|
}
|
|
|
|
# Recalculate new wake-up value
|
|
my $seconds = UConv::hms2s($wakeupTime) - $wakeupOffset * 60;
|
|
if ( $seconds < 0 ) { $seconds = 86400 + $seconds }
|
|
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "wakeupGetBegin result: $wakeupTime = $seconds s - $wakeupOffset m = "
|
|
. UConv::s2hms($seconds);
|
|
|
|
return UConv::s2hms($seconds);
|
|
}
|
|
|
|
sub RESIDENTStk_wakeupRun($;$) {
|
|
my ( $NAME, $forceRun ) = @_;
|
|
|
|
unless ( IsDevice($NAME) ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "Run function RESIDENTStk_wakeupRun() for non-existing device!";
|
|
return "$NAME: Non-existing device";
|
|
}
|
|
|
|
my $wakeupMacro = AttrVal( $NAME, "wakeupMacro", 0 );
|
|
my $wakeupDefaultTime =
|
|
ReadingsVal( $NAME, "wakeupDefaultTime",
|
|
AttrVal( $NAME, "wakeupDefaultTime", 0 ) );
|
|
my $wakeupAtdevice = AttrVal( $NAME, "wakeupAtdevice", 0 );
|
|
my $wakeupUserdevice = AttrVal( $NAME, "wakeupUserdevice", 0 );
|
|
my $wakeupDays =
|
|
ReadingsVal( $NAME, "wakeupDays", AttrVal( $NAME, "wakeupDays", "" ) );
|
|
my $wakeupHolidays =
|
|
ReadingsVal( $NAME, "wakeupHolidays",
|
|
AttrVal( $NAME, "wakeupHolidays", "" ) );
|
|
my $wakeupResetdays =
|
|
ReadingsVal( $NAME, "wakeupResetdays",
|
|
AttrVal( $NAME, "wakeupResetdays", "" ) );
|
|
my $wakeupOffset =
|
|
ReadingsVal( $NAME, "wakeupOffset", AttrVal( $NAME, "wakeupOffset", 0 ) );
|
|
my $wakeupEnforced =
|
|
ReadingsVal( $NAME, "wakeupEnforced",
|
|
AttrVal( $NAME, "wakeupEnforced", 0 ) );
|
|
my $wakeupResetSwitcher = AttrVal( $NAME, "wakeupResetSwitcher", 0 );
|
|
my $wakeupWaitPeriod = AttrVal( $NAME, "wakeupWaitPeriod", 360 );
|
|
my $holidayDevices = AttrVal( "global", "holiday2we", 0 );
|
|
my $lastRun = ReadingsVal( $NAME, "lastRun", "06:00" );
|
|
my $nextRun = ReadingsVal( $NAME, "nextRun", "06:00" );
|
|
my $wakeupUserdeviceState = ReadingsVal( $wakeupUserdevice, "state", 0 );
|
|
my $wakeupUserdeviceWakeup = ReadingsVal( $wakeupUserdevice, "wakeup", 0 );
|
|
my $room = AttrVal( $NAME, "room", 0 );
|
|
my $running = 0;
|
|
my $preventRun = 0;
|
|
my $holidayToday = 0;
|
|
|
|
if ( $wakeupHolidays ne "" ) {
|
|
foreach my $holidayDevice ( split( ',', $holidayDevices ) ) {
|
|
$holidayToday = 1
|
|
unless ( !IsDevice( $holidayDevice, "holiday" )
|
|
|| ReadingsVal( $holidayDevice, "state", "none" ) eq "none" );
|
|
}
|
|
$wakeupHolidays = "" unless ($holidayToday);
|
|
}
|
|
|
|
my ( $sec, $min, $hour, $mday, $mon, $year, $today, $yday, $isdst ) =
|
|
localtime( time() + $wakeupOffset * 60 );
|
|
|
|
$year += 1900;
|
|
$mon++;
|
|
$mon = "0" . $mon if ( $mon < 10 );
|
|
$mday = "0" . $mday if ( $mday < 10 );
|
|
$hour = "0" . $hour if ( $hour < 10 );
|
|
$min = "0" . $min if ( $min < 10 );
|
|
$sec = "0" . $sec if ( $sec < 10 );
|
|
|
|
my $nowRun = $hour . ":" . $min;
|
|
my $nowRunSec =
|
|
time_str2num( $year . "-"
|
|
. $mon . "-"
|
|
. $mday . " "
|
|
. $hour . ":"
|
|
. $min . ":"
|
|
. $sec );
|
|
|
|
if ( $nextRun ne $nowRun ) {
|
|
$lastRun = $nowRun;
|
|
Log3 $NAME, 4, "RESIDENTStk $NAME: " . "lastRun != nextRun = $lastRun";
|
|
}
|
|
else {
|
|
$lastRun = $nextRun;
|
|
Log3 $NAME, 4, "RESIDENTStk $NAME: " . "lastRun = nextRun = $lastRun";
|
|
}
|
|
|
|
my @days = ($today);
|
|
@days = split /,/, $wakeupDays
|
|
if ( $wakeupDays ne "" );
|
|
my %days = map { $_ => 1 } @days;
|
|
|
|
my @rdays = ($today);
|
|
@rdays = split /,/, $wakeupResetdays
|
|
if ( $wakeupResetdays ne "" );
|
|
my %rdays = map { $_ => 1 } @rdays;
|
|
|
|
if ( IsDisabled($NAME) ) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "wakeupDevice disabled - not triggering wake-up program";
|
|
}
|
|
elsif ( IsDisabled($wakeupUserdevice) ) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "wakeupUserdevice disabled - not triggering wake-up program";
|
|
}
|
|
elsif ( !$wakeupUserdevice ) {
|
|
return "$NAME: missing attribute wakeupUserdevice";
|
|
}
|
|
elsif ( !IsDevice($wakeupUserdevice) ) {
|
|
return "$NAME: Non existing wakeupUserdevice $wakeupUserdevice";
|
|
}
|
|
elsif ( !IsDevice( $wakeupUserdevice, "RESIDENTS|ROOMMATE|GUEST|PET" ) ) {
|
|
return "$NAME: "
|
|
. "wakeupUserdevice $wakeupUserdevice is not of type RESIDENTS, ROOMMATE, GUEST or PET";
|
|
}
|
|
elsif ( IsDevice( $wakeupUserdevice, "GUEST" )
|
|
&& $wakeupUserdeviceState eq "none" )
|
|
{
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "GUEST device $wakeupUserdevice has status value 'none' so let's disable this wake-up timer";
|
|
fhem "set $NAME nextRun OFF";
|
|
return;
|
|
}
|
|
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}
|
|
&& ( $wakeupHolidays eq ""
|
|
|| $wakeupHolidays eq "andHoliday"
|
|
|| $wakeupHolidays eq "andNoHoliday" )
|
|
)
|
|
{
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "weekday restriction in use - not triggering wake-up program this time";
|
|
}
|
|
elsif (
|
|
!$forceRun
|
|
|
|
# && !$days{$today}
|
|
# && (
|
|
# ( $wakeupHolidays eq "andHoliday" && !$holidayToday )
|
|
# || ( $wakeupHolidays eq "andNoHoliday"
|
|
# && $holidayToday )
|
|
# || ( $wakeupHolidays eq "orHoliday" && !$holidayToday )
|
|
# || ( $wakeupHolidays eq "orNoHoliday"
|
|
# && $holidayToday )
|
|
# )
|
|
&& (
|
|
(
|
|
$days{$today}
|
|
&& ( ( $wakeupHolidays eq "andHoliday" && !$holidayToday )
|
|
|| ( $wakeupHolidays eq "andNoHoliday" && $holidayToday ) )
|
|
)
|
|
|| (
|
|
!$days{$today}
|
|
&& ( ( $wakeupHolidays eq "orHoliday" && !$holidayToday )
|
|
|| ( $wakeupHolidays eq "orNoHoliday" && $holidayToday ) )
|
|
)
|
|
)
|
|
)
|
|
{
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "holiday restriction $wakeupHolidays in use - not triggering wake-up program this time";
|
|
}
|
|
elsif ($wakeupUserdeviceState eq "absent"
|
|
|| $wakeupUserdeviceState eq "gone"
|
|
|| $wakeupUserdeviceState eq "gotosleep"
|
|
|| $wakeupUserdeviceState eq "awoken" )
|
|
{
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "we should not start any wake-up program for resident device $wakeupUserdevice being in state '"
|
|
. $wakeupUserdeviceState
|
|
. "' - not triggering wake-up program this time";
|
|
}
|
|
|
|
# general conditions to trigger program fulfilled
|
|
else {
|
|
|
|
my $expLastWakeup = time_str2num(
|
|
ReadingsTimestamp(
|
|
$wakeupUserdevice, "lastWakeup", "1970-01-01 00:00:00"
|
|
)
|
|
) - 1 +
|
|
$wakeupOffset * 60 +
|
|
$wakeupWaitPeriod * 60;
|
|
|
|
my $expLastAwake = time_str2num(
|
|
ReadingsTimestamp(
|
|
$wakeupUserdevice, "lastAwake", "1970-01-01 00:00:00"
|
|
)
|
|
) - 1 +
|
|
$wakeupWaitPeriod * 60;
|
|
|
|
if ( !$wakeupMacro ) {
|
|
return "$NAME: missing attribute wakeupMacro";
|
|
}
|
|
elsif ( !IsDevice($wakeupMacro) ) {
|
|
return "$NAME: "
|
|
. "notify macro $wakeupMacro not found - no wakeup actions defined!";
|
|
}
|
|
elsif ( !IsDevice( $wakeupMacro, "notify" ) ) {
|
|
return "$NAME: device $wakeupMacro is not of type notify";
|
|
}
|
|
elsif ($wakeupUserdeviceWakeup) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "Another wake-up program is already being executed for device $wakeupUserdevice, won't trigger $wakeupMacro";
|
|
}
|
|
elsif ( $expLastWakeup > $nowRunSec && !$forceRun ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "won't trigger wake-up program due to non-expired wakeupWaitPeriod threshold since lastWakeup (expLastWakeup=$expLastWakeup > nowRunSec=$nowRunSec)";
|
|
}
|
|
elsif ( $expLastAwake > $nowRunSec && !$forceRun ) {
|
|
Log3 $NAME, 3,
|
|
"RESIDENTStk $NAME: "
|
|
. "won't trigger wake-up program due to non-expired wakeupWaitPeriod threshold since lastAwake (expLastAwake=$expLastAwake > nowRunSec=$nowRunSec)";
|
|
}
|
|
else {
|
|
|
|
# conditional enforced wake-up:
|
|
# only if actual wake-up time is
|
|
# earlier than wakeupDefaultTime
|
|
if ( $wakeupEnforced == 3
|
|
&& $wakeupDefaultTime
|
|
&& UConv::hms2s($wakeupDefaultTime) > UConv::hms2s($lastRun) )
|
|
{
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "Enforcing wake-up because wake-up time is earlier than normal (wakeupDefaultTime=$wakeupDefaultTime > lastRun=$lastRun)";
|
|
$wakeupEnforced = 1;
|
|
}
|
|
|
|
# conditional enforced wake-up:
|
|
# only if actual wake-up time is not wakeupDefaultTime
|
|
elsif ($wakeupEnforced == 2
|
|
&& $wakeupDefaultTime
|
|
&& $wakeupDefaultTime ne $lastRun )
|
|
{
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "Enforcing wake-up because wake-up is different from normal (wakeupDefaultTime=$wakeupDefaultTime =! lastRun=$lastRun)";
|
|
$wakeupEnforced = 1;
|
|
}
|
|
elsif ( $wakeupEnforced > 1 ) {
|
|
$wakeupEnforced = 0;
|
|
}
|
|
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: " . "trigger $wakeupMacro (running=1)";
|
|
fhem "trigger $wakeupMacro "
|
|
. "start $lastRun $wakeupOffset $wakeupEnforced $wakeupUserdevice $wakeupUserdeviceState";
|
|
|
|
# Update user device with last wakeup details
|
|
#
|
|
readingsBeginUpdate( $defs{$wakeupUserdevice} );
|
|
readingsBulkUpdate( $defs{$wakeupUserdevice},
|
|
"lastWakeup", $lastRun );
|
|
readingsBulkUpdate( $defs{$wakeupUserdevice},
|
|
"lastWakeupDev", $NAME );
|
|
readingsBulkUpdate( $defs{$wakeupUserdevice}, "wakeup", "1" );
|
|
readingsEndUpdate( $defs{$wakeupUserdevice}, 1 );
|
|
|
|
readingsSingleUpdate( $defs{$wakeupUserdevice}, "wakeup", "0", 1 )
|
|
unless ($wakeupOffset);
|
|
|
|
readingsSingleUpdate( $defs{$NAME}, "lastRun", $lastRun, 1 );
|
|
|
|
if ($wakeupOffset) {
|
|
my $wakeupStopAtdevice = $wakeupAtdevice . "_stop";
|
|
fhem "delete $wakeupStopAtdevice"
|
|
if ( IsDevice($wakeupStopAtdevice) );
|
|
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: "
|
|
. "created at-device $wakeupStopAtdevice to stop wake-up program in $wakeupOffset minutes";
|
|
fhem "define $wakeupStopAtdevice at +"
|
|
. UConv::s2hms( $wakeupOffset * 60 + 1 )
|
|
. " set $NAME:FILTER=running=1 stop triggerpost";
|
|
fhem "attr $wakeupStopAtdevice "
|
|
. "comment Auto-created by RESIDENTS Toolkit: temp. at-device to stop wake-up program of timer $NAME when wake-up time is reached";
|
|
|
|
$running = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $running && $wakeupOffset ) {
|
|
readingsBeginUpdate( $defs{$NAME} );
|
|
readingsBulkUpdate( $defs{$NAME}, "running", "1" );
|
|
readingsBulkUpdate( $defs{$NAME}, "state", "running" );
|
|
readingsEndUpdate( $defs{$NAME}, 1 );
|
|
}
|
|
|
|
# Update user device with next wakeup details
|
|
#
|
|
readingsBeginUpdate( $defs{$wakeupUserdevice} );
|
|
my ( $nextWakeupDev, $nextWakeup ) =
|
|
RESIDENTStk_wakeupGetNext( $wakeupUserdevice, $NAME );
|
|
if ( !$nextWakeupDev || !$nextWakeup ) {
|
|
$nextWakeupDev = "";
|
|
$nextWakeup = "OFF";
|
|
}
|
|
readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
|
|
"nextWakeupDev", $nextWakeupDev );
|
|
readingsBulkUpdateIfChanged( $defs{$wakeupUserdevice},
|
|
"nextWakeup", $nextWakeup );
|
|
readingsEndUpdate( $defs{$wakeupUserdevice}, 1 );
|
|
|
|
my $doReset = 1;
|
|
if ( IsDevice( $wakeupResetSwitcher, "dummy" )
|
|
&& ReadingsVal( $wakeupResetSwitcher, "state", 0 ) eq "off" )
|
|
{
|
|
$doReset = 0;
|
|
}
|
|
|
|
if ( $wakeupDefaultTime && $rdays{$today} && $doReset ) {
|
|
Log3 $NAME, 4,
|
|
"RESIDENTStk $NAME: " . "Resetting based on wakeupDefaultTime";
|
|
fhem "set $NAME:FILTER=state!=$wakeupDefaultTime "
|
|
. "nextRun $wakeupDefaultTime";
|
|
}
|
|
elsif ( !$running ) {
|
|
readingsBeginUpdate( $defs{$NAME} );
|
|
readingsBulkUpdateIfChanged( $defs{$NAME}, "state", $nextRun );
|
|
readingsEndUpdate( $defs{$NAME}, 1 );
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
sub RESIDENTStk_wakeupGetNext($;$) {
|
|
my ( $name, $wakeupDeviceRunning ) = @_;
|
|
my $wakeupDeviceAttrName = "";
|
|
|
|
$wakeupDeviceAttrName = "rgr_wakeupDevice"
|
|
if ( defined( $attr{$name}{"rgr_wakeupDevice"} ) );
|
|
$wakeupDeviceAttrName = "rr_wakeupDevice"
|
|
if ( defined( $attr{$name}{"rr_wakeupDevice"} ) );
|
|
$wakeupDeviceAttrName = "rp_wakeupDevice"
|
|
if ( defined( $attr{$name}{"rp_wakeupDevice"} ) );
|
|
$wakeupDeviceAttrName = "rg_wakeupDevice"
|
|
if ( defined( $attr{$name}{"rg_wakeupDevice"} ) );
|
|
|
|
my $wakeupDeviceList = AttrVal( $name, $wakeupDeviceAttrName, 0 );
|
|
|
|
my ( $sec, $min, $hour, $mday, $mon, $year, $today, $yday, $isdst ) =
|
|
localtime(time);
|
|
|
|
$hour = "0" . $hour if ( $hour < 10 );
|
|
$min = "0" . $min if ( $min < 10 );
|
|
|
|
my $tomorrow = $today + 1;
|
|
$tomorrow = 0 if ( $tomorrow == 7 );
|
|
my $secNow = UConv::hms2s( $hour . ":" . $min ) + $sec;
|
|
my $definitiveNextToday;
|
|
my $definitiveNextTomorrow;
|
|
my $definitiveNextTodayDev;
|
|
my $definitiveNextTomorrowDev;
|
|
|
|
my $holidayDevices = AttrVal( "global", "holiday2we", 0 );
|
|
|
|
# check for each registered wake-up device
|
|
for my $wakeupDevice ( split /,/, $wakeupDeviceList ) {
|
|
if ( !IsDevice($wakeupDevice) ) {
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $name: "
|
|
. "00 - ignoring reference to non-existing wakeupDevice $wakeupDevice";
|
|
|
|
my $wakeupDeviceListNew = $wakeupDeviceList;
|
|
$wakeupDeviceListNew =~ s/,$wakeupDevice,/,/g;
|
|
$wakeupDeviceListNew =~ s/$wakeupDevice,//g;
|
|
$wakeupDeviceListNew =~ s/,$wakeupDevice//g;
|
|
|
|
if ( $wakeupDeviceListNew ne $wakeupDeviceList ) {
|
|
Log3 $name, 3,
|
|
"RESIDENTStk $name: "
|
|
. "reference to non-existing wakeupDevice '$wakeupDevice' was removed";
|
|
fhem "attr $name $wakeupDeviceAttrName $wakeupDeviceListNew";
|
|
}
|
|
|
|
next;
|
|
}
|
|
|
|
my $wakeupAtdevice = AttrVal( $wakeupDevice, "wakeupAtdevice", undef );
|
|
my $wakeupOffset =
|
|
ReadingsVal( $wakeupDevice, "wakeupOffset",
|
|
AttrVal( $wakeupDevice, "wakeupOffset", 0 ) );
|
|
my $wakeupAtNTM = (
|
|
IsDevice($wakeupAtdevice)
|
|
&& defined( $defs{$wakeupAtdevice}{NTM} )
|
|
? substr( $defs{$wakeupAtdevice}{NTM}, 0, -3 )
|
|
: undef
|
|
);
|
|
my $wakeupDays =
|
|
ReadingsVal( $wakeupDevice, "wakeupDays",
|
|
AttrVal( $wakeupDevice, "wakeupDays", "" ) );
|
|
my $wakeupHolidays =
|
|
ReadingsVal( $wakeupDevice, "wakeupHolidays",
|
|
AttrVal( $wakeupDevice, "wakeupHolidays", "" ) );
|
|
my $holidayToday = 0;
|
|
my $holidayTomorrow = 0;
|
|
my $nextRunSrc;
|
|
my $nextRun = ReadingsVal( $wakeupDevice, "nextRun", undef );
|
|
my $ltoday = $today;
|
|
my $ltomorrow = $tomorrow;
|
|
|
|
if ( IsDisabled($wakeupDevice)
|
|
|| !$nextRun
|
|
|| lc($nextRun) eq "off"
|
|
|| $nextRun !~ /^([0-9]{2}:[0-9]{2})$/ )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $name: "
|
|
. "00 - ignoring disabled wakeupDevice $wakeupDevice";
|
|
next;
|
|
}
|
|
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $name: "
|
|
. "00 - checking for next wake-up candidate $wakeupDevice";
|
|
|
|
# get holiday status for today and tomorrow
|
|
if ( $wakeupHolidays eq "" ) {
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: 01 - Not considering any holidays";
|
|
}
|
|
else {
|
|
foreach my $holidayDevice ( split( ',', $holidayDevices ) ) {
|
|
if ( IsDevice( $holidayDevice, "holiday" ) ) {
|
|
$holidayToday = 1
|
|
unless (
|
|
ReadingsVal( $holidayDevice, "state", "none" ) eq
|
|
"none" );
|
|
$holidayTomorrow = 1
|
|
unless (
|
|
ReadingsVal( $holidayDevice, "tomorrow", "none" ) eq
|
|
"none" );
|
|
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "01 - Holidays to be considered ($wakeupHolidays) - holidayToday=$holidayToday holidayTomorrow=$holidayTomorrow";
|
|
}
|
|
}
|
|
}
|
|
|
|
# set day scope for today
|
|
my @days = ($ltoday);
|
|
@days = split /,/, $wakeupDays
|
|
if ( $wakeupDays ne "" );
|
|
my %days = map { $_ => 1 } @days;
|
|
|
|
# set day scope for tomorrow
|
|
my @daysTomorrow = ($ltomorrow);
|
|
@daysTomorrow = split /,/, $wakeupDays
|
|
if ( $wakeupDays ne "" );
|
|
my %daysTomorrow = map { $_ => 1 } @daysTomorrow;
|
|
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "02 - possible candidate found - weekdayToday=$ltoday weekdayTomorrow=$ltomorrow";
|
|
|
|
my $nextRunSec;
|
|
my $nextRunSecTarget;
|
|
|
|
# Use direct information from at-device if possible
|
|
if ( $wakeupAtNTM
|
|
&& $wakeupAtNTM =~ /^([0-9]{2}:[0-9]{2})$/ )
|
|
{
|
|
$nextRunSrc = "at";
|
|
$nextRunSec = UConv::hms2s($wakeupAtNTM);
|
|
$nextRunSecTarget = $nextRunSec + $wakeupOffset * 60;
|
|
|
|
if ( $wakeupOffset && $nextRunSecTarget >= 86400 ) {
|
|
$nextRunSecTarget -= 86400;
|
|
|
|
$ltoday++;
|
|
$ltoday = $ltoday - 7
|
|
if ( $ltoday > 6 );
|
|
|
|
$ltomorrow++;
|
|
$ltomorrow = $ltomorrow - 7
|
|
if ( $ltomorrow > 6 );
|
|
}
|
|
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "03 - considering at-device value wakeupAtNTM=$wakeupAtNTM wakeupOffset=$wakeupOffset nextRunSec=$nextRunSec nextRunSecTarget=$nextRunSecTarget";
|
|
}
|
|
else {
|
|
$nextRunSrc = "dummy";
|
|
$nextRunSecTarget = UConv::hms2s($nextRun);
|
|
$nextRunSec = $nextRunSecTarget - $wakeupOffset * 60;
|
|
|
|
if ( $wakeupOffset && $nextRunSec < 0 ) {
|
|
$nextRunSec += 86400;
|
|
|
|
$ltoday--;
|
|
$ltoday = $ltoday + 7
|
|
if ( $ltoday < 0 );
|
|
|
|
$ltomorrow--;
|
|
$ltomorrow = $ltomorrow + 7
|
|
if ( $ltomorrow < 0 );
|
|
}
|
|
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "03 - considering dummy-device value nextRun=$nextRun wakeupOffset=$wakeupOffset nextRunSec=$nextRunSec nextRunSecTarget=$nextRunSecTarget (wakeupAtNTM=$wakeupAtNTM)";
|
|
}
|
|
|
|
# still running today
|
|
if ( $nextRunSec > $secNow ) {
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "04 - this is a candidate for today - weekdayToday=$ltoday";
|
|
|
|
# if today is in scope
|
|
if ( $days{$ltoday} ) {
|
|
|
|
# if we need to consider holidays in addition
|
|
if (
|
|
( $wakeupHolidays eq "andHoliday" && !$holidayToday )
|
|
|| ( $wakeupHolidays eq "andNoHoliday"
|
|
&& $holidayToday )
|
|
)
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "05 - no run today due to holiday based on combined weekday and holiday decision";
|
|
next;
|
|
}
|
|
|
|
# easy if there is no holiday dependency
|
|
elsif ( !$definitiveNextToday
|
|
|| $nextRunSec < $definitiveNextToday )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "05 - until now, will be NEXT WAKE-UP RUN today based on weekday decision";
|
|
$definitiveNextToday = $nextRunSec;
|
|
$definitiveNextTodayDev = $wakeupDevice;
|
|
}
|
|
}
|
|
|
|
elsif ($wakeupHolidays eq ""
|
|
|| $wakeupHolidays eq "andHoliday"
|
|
|| $wakeupHolidays eq "andNoHoliday" )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "05 - won't be running today anymore based on weekday decision";
|
|
next;
|
|
}
|
|
|
|
# if we need to consider holidays in parallel to weekdays
|
|
elsif (( $wakeupHolidays eq "orHoliday" && !$holidayToday )
|
|
|| ( $wakeupHolidays eq "orNoHoliday" && $holidayToday ) )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "06 - won't be running today based on holiday decision";
|
|
next;
|
|
}
|
|
|
|
# easy if there is no holiday dependency
|
|
elsif ( !$definitiveNextToday
|
|
|| $nextRunSec < $definitiveNextToday )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "06 - until now, will be NEXT WAKE-UP RUN today based on holiday decision";
|
|
$definitiveNextToday = $nextRunSec;
|
|
$definitiveNextTodayDev = $wakeupDevice;
|
|
}
|
|
}
|
|
|
|
# running later
|
|
else {
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "04 - this is a candidate for tomorrow or later - weekdayTomorrow=$ltomorrow";
|
|
|
|
# if tomorrow is in scope
|
|
if ( $daysTomorrow{$ltomorrow} ) {
|
|
|
|
# if we need to consider holidays in addition
|
|
if (
|
|
( $wakeupHolidays eq "andHoliday" && !$holidayTomorrow )
|
|
|| ( $wakeupHolidays eq "andNoHoliday"
|
|
&& $holidayTomorrow )
|
|
)
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "05 - no run tomorrow due to holiday based on combined weekday and holiday decision";
|
|
next;
|
|
}
|
|
|
|
# easy if there is no holiday dependency
|
|
elsif ( !$definitiveNextTomorrow
|
|
|| $nextRunSec < $definitiveNextTomorrow )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "05 - until now, will be NEXT WAKE-UP RUN tomorrow based on weekday decision";
|
|
$definitiveNextTomorrow = $nextRunSec;
|
|
$definitiveNextTomorrowDev = $wakeupDevice;
|
|
}
|
|
}
|
|
|
|
elsif ($wakeupHolidays eq ""
|
|
|| $wakeupHolidays eq "andHoliday"
|
|
|| $wakeupHolidays eq "andNoHoliday" )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "05 - won't be running tomorrow based on weekday decision";
|
|
next;
|
|
}
|
|
|
|
# if we need to consider holidays in parallel to weekdays
|
|
elsif (
|
|
( $wakeupHolidays eq "orHoliday" && !$holidayTomorrow )
|
|
|| ( $wakeupHolidays eq "orNoHoliday"
|
|
&& $holidayTomorrow )
|
|
)
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "06 - won't be running tomorrow based on holiday decision";
|
|
next;
|
|
}
|
|
|
|
# easy if there is no holiday dependency
|
|
elsif ( !$definitiveNextTomorrow
|
|
|| $nextRunSec < $definitiveNextTomorrow )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $wakeupDevice: "
|
|
. "06 - until now, will be NEXT WAKE-UP RUN tomorrow based on holiday decision";
|
|
$definitiveNextTomorrow = $nextRunSec;
|
|
$definitiveNextTomorrowDev = $wakeupDevice;
|
|
}
|
|
}
|
|
|
|
if ($wakeupOffset) {
|
|
|
|
# add Offset
|
|
$definitiveNextToday += $wakeupOffset * 60
|
|
if ( defined($definitiveNextToday) );
|
|
$definitiveNextTomorrow += $wakeupOffset * 60
|
|
if ( defined($definitiveNextTomorrow) );
|
|
|
|
# correct change over midnight
|
|
if ( defined($definitiveNextToday) ) {
|
|
if ( $definitiveNextToday >= 86400 ) {
|
|
$definitiveNextToday -= 86400;
|
|
}
|
|
elsif ( $definitiveNextToday < 0 ) {
|
|
$definitiveNextToday += 86400;
|
|
}
|
|
}
|
|
if ( defined($definitiveNextTomorrow) ) {
|
|
if ( $definitiveNextTomorrow >= 86400 ) {
|
|
$definitiveNextTomorrow -= 86400;
|
|
}
|
|
elsif ( $definitiveNextTomorrow < 0 ) {
|
|
$definitiveNextTomorrow += 86400;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( defined($definitiveNextTodayDev)
|
|
&& defined($definitiveNextToday) )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $name: 07 - next wake-up result: today at "
|
|
. UConv::s2hms($definitiveNextToday)
|
|
. ", wakeupDevice="
|
|
. $definitiveNextTodayDev;
|
|
|
|
return ( $definitiveNextTodayDev,
|
|
substr( UConv::s2hms($definitiveNextToday), 0, -3 ) );
|
|
}
|
|
elsif (defined($definitiveNextTomorrowDev)
|
|
&& defined($definitiveNextTomorrow) )
|
|
{
|
|
Log3 $name, 4,
|
|
"RESIDENTStk $name: 07 - next wake-up result: tomorrow at "
|
|
. UConv::s2hms($definitiveNextTomorrow)
|
|
. ", wakeupDevice="
|
|
. $definitiveNextTomorrowDev;
|
|
|
|
return ( $definitiveNextTomorrowDev,
|
|
substr( UConv::s2hms($definitiveNextTomorrow), 0, -3 ) );
|
|
}
|
|
|
|
return ( undef, undef );
|
|
}
|
|
|
|
sub RESIDENTStk_TimeSum($$) {
|
|
my ( $val1, $val2 ) = @_;
|
|
my ( $timestamp1, $timestamp2, $math );
|
|
|
|
if ( $val1 !~ /^([0-9]{2}):([0-9]{2})$/ ) {
|
|
return $val1;
|
|
}
|
|
else {
|
|
$timestamp1 = UConv::hms2s($val1);
|
|
}
|
|
|
|
if ( $val2 =~ /^([\+\-])([0-9]{2}):([0-9]{2})$/ ) {
|
|
$math = $1;
|
|
$timestamp2 = UConv::hms2s("$2:$3");
|
|
}
|
|
elsif ( $val2 =~ /^([\+\-])([0-9]*)$/ ) {
|
|
$math = $1;
|
|
$timestamp2 = $2 * 60;
|
|
}
|
|
else {
|
|
return $val1;
|
|
}
|
|
|
|
if ( $math eq "-" ) {
|
|
return substr( UConv::s2hms( $timestamp1 - $timestamp2 ), 0, -3 );
|
|
}
|
|
else {
|
|
return substr( UConv::s2hms( $timestamp1 + $timestamp2 ), 0, -3 );
|
|
}
|
|
|
|
}
|
|
|
|
sub RESIDENTStk_InternalTimer($$$$$) {
|
|
my ( $modifier, $tim, $callback, $hash, $waitIfInitNotDone ) = @_;
|
|
|
|
my $mHash;
|
|
if ( $modifier eq "" ) {
|
|
$mHash = $hash;
|
|
}
|
|
else {
|
|
my $timerName = $hash->{NAME} . "_" . $modifier;
|
|
if ( exists( $hash->{TIMER}{$timerName} ) ) {
|
|
$mHash = $hash->{TIMER}{$timerName};
|
|
}
|
|
else {
|
|
$mHash = {
|
|
HASH => $hash,
|
|
NAME => $hash->{NAME} . "_" . $modifier,
|
|
MODIFIER => $modifier
|
|
};
|
|
$hash->{TIMER}{$timerName} = $mHash;
|
|
}
|
|
}
|
|
InternalTimer( $tim, $callback, $mHash, $waitIfInitNotDone );
|
|
}
|
|
|
|
sub RESIDENTStk_RemoveInternalTimer($$) {
|
|
my ( $modifier, $hash ) = @_;
|
|
|
|
my $timerName = $hash->{NAME} . "_" . $modifier;
|
|
if ( $modifier eq "" ) {
|
|
RemoveInternalTimer($hash);
|
|
}
|
|
else {
|
|
my $mHash = $hash->{TIMER}{$timerName};
|
|
if ( defined($mHash) ) {
|
|
delete $hash->{TIMER}{$timerName};
|
|
RemoveInternalTimer($mHash);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub RESIDENTStk_StartInternalTimers($$) {
|
|
my ($hash) = @_;
|
|
|
|
RESIDENTStk_AutoGone($hash);
|
|
RESIDENTStk_DurationTimer($hash);
|
|
}
|
|
|
|
sub RESIDENTStk_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, $ret ) = @_;
|
|
my @slaves;
|
|
|
|
my @ROOMMATES;
|
|
foreach ( devspec2array("TYPE=ROOMMATE") ) {
|
|
next
|
|
unless (
|
|
defined( $defs{$_}{RESIDENTGROUPS} )
|
|
&& grep { $hash->{NAME} eq $_ }
|
|
split( /,/, $defs{$_}{RESIDENTGROUPS} )
|
|
);
|
|
push @ROOMMATES, $_;
|
|
}
|
|
|
|
my @GUESTS;
|
|
foreach ( devspec2array("TYPE=GUEST") ) {
|
|
next
|
|
unless (
|
|
defined( $defs{$_}{RESIDENTGROUPS} )
|
|
&& grep { $hash->{NAME} eq $_ }
|
|
split( /,/, $defs{$_}{RESIDENTGROUPS} )
|
|
);
|
|
push @GUESTS, $_;
|
|
}
|
|
|
|
my @PETS;
|
|
foreach ( devspec2array("TYPE=PET") ) {
|
|
next
|
|
unless (
|
|
defined( $defs{$_}{RESIDENTGROUPS} )
|
|
&& grep { $hash->{NAME} eq $_ }
|
|
split( /,/, $defs{$_}{RESIDENTGROUPS} )
|
|
);
|
|
push @PETS, $_;
|
|
}
|
|
|
|
if ( scalar @ROOMMATES ) {
|
|
$hash->{ROOMMATES} = join( ",", @ROOMMATES );
|
|
}
|
|
elsif ( $hash->{ROOMMATES} ) {
|
|
delete $hash->{ROOMMATES};
|
|
}
|
|
|
|
if ( scalar @GUESTS ) {
|
|
$hash->{GUESTS} = join( ",", @GUESTS );
|
|
}
|
|
elsif ( $hash->{GUESTS} ) {
|
|
delete $hash->{GUESTS};
|
|
}
|
|
|
|
if ( scalar @PETS ) {
|
|
$hash->{PETS} = join( ",", @PETS );
|
|
}
|
|
elsif ( $hash->{PETS} ) {
|
|
delete $hash->{PETS};
|
|
}
|
|
|
|
if ( $hash->{ROOMMATES} ) {
|
|
$ret .= "," if ($ret);
|
|
$ret .= $hash->{ROOMMATES};
|
|
}
|
|
if ( $hash->{GUESTS} ) {
|
|
$ret .= "," if ($ret);
|
|
$ret .= $hash->{GUESTS};
|
|
}
|
|
if ( $hash->{PETS} ) {
|
|
$ret .= "," if ($ret);
|
|
$ret .= $hash->{PETS};
|
|
}
|
|
|
|
return RESIDENTStk_findDummySlaves( $hash, $ret );
|
|
}
|
|
|
|
sub RESIDENTStk_findDummySlaves($;$);
|
|
|
|
sub RESIDENTStk_findDummySlaves($;$) {
|
|
my ( $hash, $ret ) = @_;
|
|
my $prefix = RESIDENTStk_GetPrefixFromType( $hash->{NAME} );
|
|
my $wakeupDevice =
|
|
AttrVal( $hash->{NAME}, $prefix . "wakeupDevice", undef );
|
|
my $presenceDevices =
|
|
AttrVal( $hash->{NAME}, $prefix . "presenceDevices", undef );
|
|
$ret = "" unless ($ret);
|
|
|
|
# add r_*wakeupDevice
|
|
if ($wakeupDevice) {
|
|
$ret .= "," if ( $ret ne "" );
|
|
$ret .= $wakeupDevice;
|
|
|
|
# wakeupResetSwitcher
|
|
foreach ( split( ',', $wakeupDevice ) ) {
|
|
my $rsw = AttrVal( $_, "wakeupResetSwitcher", "" );
|
|
if ( $rsw =~ /^[a-zA-Z\d._]+$/ ) {
|
|
$ret .= "," if ( $ret ne "" );
|
|
$ret .= $rsw;
|
|
}
|
|
}
|
|
}
|
|
|
|
# add r_*presenceDevices
|
|
if ($presenceDevices) {
|
|
foreach ($presenceDevices) {
|
|
my $d = $_;
|
|
$d =~ s/:.*$//g;
|
|
$ret .= "," if ( $ret ne "" );
|
|
$ret .= $d;
|
|
}
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
sub RESIDENTStk_GetPrefixFromType($) {
|
|
my ($name) = @_;
|
|
return $modules{ $defs{$name}{TYPE} }{AttrPrefix}
|
|
if ( $defs{$name}
|
|
&& $defs{$name}{TYPE}
|
|
&& $modules{ $defs{$name}{TYPE} }
|
|
&& $modules{ $defs{$name}{TYPE} }{AttrPrefix} );
|
|
return "";
|
|
}
|
|
|
|
sub RESIDENTStk_DoModuleTrigger($$@) {
|
|
my ( $hash, $newState, $noreplace, $TYPE ) = @_;
|
|
$hash = $defs{$hash} unless ( ref($hash) );
|
|
$TYPE = $hash->{TYPE} unless ( defined($TYPE) );
|
|
|
|
return ""
|
|
unless ( defined($TYPE)
|
|
&& defined( $modules{$TYPE} )
|
|
&& defined($newState)
|
|
&& $newState =~
|
|
m/^([A-Za-z\d._]+)(?:\s+([A-Za-z\d._]+)(?:\s+(.*))?)?$/ );
|
|
$noreplace = 1 unless ( defined($noreplace) );
|
|
|
|
my $e = $1;
|
|
my $d = $2;
|
|
|
|
return "DoModuleTrigger() can only handle module related events"
|
|
if ( ( $hash->{NAME} && $hash->{NAME} eq "global" )
|
|
|| $d eq "global" );
|
|
return DoTrigger( "global", "$TYPE:$newState", $noreplace )
|
|
unless ( $e =~ /^INITIALIZED|INITIALIZING|MODIFIED|DELETED$/ );
|
|
return "$e: missing device name"
|
|
if ( !defined($d) || $d eq "" );
|
|
|
|
my $dret;
|
|
my @rets;
|
|
$hash = $defs{$d};
|
|
|
|
if ( !$hash || !ref($hash) ) {
|
|
$dret = "device was deleted"
|
|
unless ( $e eq "DELETED" );
|
|
$e = "DELETED";
|
|
}
|
|
elsif ($e eq "INITIALIZING"
|
|
|| ( $hash->{READY} && $hash->{MOD_INIT} )
|
|
|| ( $modules{$TYPE}{READY} && $hash->{MOD_INIT} ) )
|
|
{
|
|
$dret = "device initialization started";
|
|
Log 4, "$TYPE $d: $dret";
|
|
$hash->{MOD_INIT} = 1;
|
|
$e = "INITIALIZING";
|
|
}
|
|
elsif ( $hash->{MOD_INIT} ) {
|
|
$dret = "pending device initialization";
|
|
Log 4, "$TYPE $d: $dret";
|
|
}
|
|
elsif ( !$hash->{READY} ) {
|
|
$e = "INITIALIZED";
|
|
$hash->{READY} = 1;
|
|
Log 4, "$TYPE $d: device initialization completed";
|
|
}
|
|
else {
|
|
$e = "MODIFIED";
|
|
}
|
|
|
|
my $ret = DoTrigger( "global", "$TYPE:$e $d", $noreplace )
|
|
if ( $e ne "DELETED"
|
|
&& ( !$hash->{MOD_INIT} || $e eq "INITIALIZING" ) );
|
|
push @rets, $ret if ( defined($ret) && $ret ne "" );
|
|
|
|
####
|
|
## track module readiness
|
|
|
|
my $initcnt = scalar devspec2array("TYPE=$TYPE:FILTER=MOD_INIT=.+");
|
|
|
|
# no more devices of that module
|
|
if ( scalar devspec2array("TYPE=$TYPE") == 0 ) {
|
|
delete $modules{$TYPE}{READY};
|
|
delete $modules{$TYPE}{INIT};
|
|
Log 4, "$TYPE: module is no more in use";
|
|
my $ret = DoTrigger( "global", "$TYPE:DELETED", $noreplace );
|
|
push @rets, $ret if ( defined($ret) && $ret ne "" );
|
|
}
|
|
|
|
# all devices completed initialization after bootup
|
|
elsif ( !$modules{$TYPE}{READY} && !$initcnt ) {
|
|
$modules{$TYPE}{READY} = 1;
|
|
delete $modules{$TYPE}{INIT};
|
|
Log 4, "$TYPE: module initialization completed";
|
|
my $ret = DoTrigger( "global", "$TYPE:INITIALIZED", $noreplace );
|
|
push @rets, $ret if ( defined($ret) && $ret ne "" );
|
|
}
|
|
|
|
# pending devices during bootup
|
|
elsif ( !$modules{$TYPE}{READY} && !$modules{$TYPE}{INIT} && $initcnt ) {
|
|
$modules{$TYPE}{INIT} = 1;
|
|
}
|
|
|
|
# pending devices during runtime
|
|
elsif ( !$modules{$TYPE}{INIT} && $initcnt ) {
|
|
$modules{$TYPE}{INIT} = 1;
|
|
Log 4, "$TYPE: module re-initialization in progress";
|
|
my $ret = DoTrigger( "global", "$TYPE:INITIALIZING", $noreplace );
|
|
push @rets, $ret if ( defined($ret) && $ret ne "" );
|
|
}
|
|
|
|
# device constellation changed during runtime
|
|
# with pending devices
|
|
elsif ($modules{$TYPE}{READY}
|
|
&& $modules{$TYPE}{INIT}
|
|
&& ( !$initcnt || $e eq "DELETED" ) )
|
|
{
|
|
delete $modules{$TYPE}{INIT};
|
|
Log 4, "$TYPE: module re-initialization completed";
|
|
my $ret = DoTrigger( "global", "$TYPE:INITIALIZED", $noreplace );
|
|
push @rets, $ret if ( defined($ret) && $ret ne "" );
|
|
}
|
|
|
|
# device constellation changed during runtime
|
|
elsif ( $modules{$TYPE}{READY} && ( !$initcnt || $e eq "DELETED" ) ) {
|
|
Log 4, "$TYPE: module constellation updated";
|
|
my $ret = DoTrigger( "global", "$TYPE:MODIFIED", $noreplace );
|
|
push @rets, $ret if ( defined($ret) && $ret ne "" );
|
|
}
|
|
|
|
unshift @rets, " $d: $dret" if ($dret);
|
|
return join( "\n", @rets );
|
|
}
|
|
|
|
sub RESIDENTStk_DoInitDev(@) {
|
|
my (@devices) = @_;
|
|
|
|
my @rets;
|
|
foreach my $d (@devices) {
|
|
$d = $d->{NAME} if ( ref($d) eq "HASH" );
|
|
next unless ( $defs{$d} );
|
|
$defs{$d}{MOD_INIT} = 1;
|
|
my $ret = CallFn( $d, "InitDevFn", $defs{$d} );
|
|
if ( defined($ret) && $ret eq "0" ) {
|
|
delete $defs{$d}{MOD_INIT};
|
|
next;
|
|
}
|
|
if ( defined($ret) && $ret ne "" ) {
|
|
$defs{$d}{MOD_INIT} = 2;
|
|
$modules{ $defs{$d}{TYPE} }{INIT} = 2;
|
|
push @rets, $ret;
|
|
}
|
|
else {
|
|
delete $defs{$d}{MOD_INIT};
|
|
}
|
|
$ret = RESIDENTStk_DoModuleTrigger( $defs{$d}, "INITIALIZED $d" );
|
|
push @rets, $ret if ( defined($ret) && $ret ne "" );
|
|
}
|
|
return join( "\n", @rets );
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=encoding utf8
|
|
|
|
=for :application/json;q=META.json RESIDENTStk.pm
|
|
{
|
|
"author": [
|
|
"Julian Pawlowski <julian.pawlowski@gmail.com>"
|
|
],
|
|
"x_fhem_maintainer": [
|
|
"loredo"
|
|
],
|
|
"x_fhem_maintainer_github": [
|
|
"jpawlowski"
|
|
],
|
|
"keywords": [
|
|
"RESIDENTS"
|
|
]
|
|
}
|
|
=end :application/json;q=META.json
|
|
|
|
=cut
|