############################################################################### # $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 $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 { '' }"; 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 " ], "x_fhem_maintainer": [ "loredo" ], "x_fhem_maintainer_github": [ "jpawlowski" ], "keywords": [ "RESIDENTS" ] } =end :application/json;q=META.json =cut