# $Id$
##############################################################################
#
# 10_RESIDENTS.pm
# An FHEM Perl module to ease resident administration.
#
# Copyright by Julian Pawlowski
# e-mail: julian.pawlowski at gmail.com
#
# This file is part of fhem.
#
# Fhem is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Fhem is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see .
#
#
# Version: 1.1.0
#
# Major Version History:
# - 1.1.0 - 2014-04-07
# -- new readings in computer readable format (*_cr)
# -- format of readings durTimer readings changed from minutes to HH:MM:ss
#
# - 1.0.0 - 2014-02-08
# -- First release
#
##############################################################################
package main;
use strict;
use warnings;
use Time::Local;
use Data::Dumper;
sub RESIDENTS_Set($@);
sub RESIDENTS_Define($$);
sub RESIDENTS_Notify($$);
sub RESIDENTS_Undefine($$);
###################################
sub RESIDENTS_Initialize($) {
my ($hash) = @_;
Log3 $hash, 5, "RESIDENTS_Initialize: Entering";
$hash->{SetFn} = "RESIDENTS_Set";
$hash->{DefFn} = "RESIDENTS_Define";
$hash->{NotifyFn} = "RESIDENTS_Notify";
$hash->{UndefFn} = "RESIDENTS_Undefine";
$hash->{AttrList} =
"rgr_showAllStates:0,1 rgr_states " . $readingFnAttributes;
}
###################################
sub RESIDENTS_Define($$) {
my ( $hash, $def ) = @_;
my $name = $hash->{NAME};
my $name_attr;
Log3 $name, 5, "RESIDENTS $name: called function RESIDENTS_Define()";
$hash->{TYPE} = "RESIDENTS";
# attr alias
$name_attr = "alias";
unless ( exists( $attr{$name}{$name_attr} ) ) {
$attr{$name}{$name_attr} = "Residents";
}
# attr devStateIcon
$name_attr = "devStateIcon";
unless ( exists( $attr{$name}{$name_attr} ) ) {
$attr{$name}{$name_attr} =
'.*home:status_available:absent .*absent:status_away_1:home .*gone:status_standby:home .*none:control_building_empty .*gotosleep:status_night:asleep .*asleep:status_night:awoken .*awoken:status_available:home';
}
# attr group
$name_attr = "group";
unless ( exists( $attr{$name}{$name_attr} ) ) {
$attr{$name}{$name_attr} = "Home State";
}
# attr icon
$name_attr = "icon";
unless ( exists( $attr{$name}{$name_attr} ) ) {
$attr{$name}{$name_attr} = "control_building_filled";
}
# attr room
$name_attr = "room";
unless ( exists( $attr{$name}{$name_attr} ) ) {
$attr{$name}{$name_attr} = "Residents";
}
# attr webCmd
$name_attr = "webCmd";
unless ( exists( $attr{$name}{$name_attr} ) ) {
$attr{$name}{$name_attr} = "state";
}
return undef;
}
###################################
sub RESIDENTS_Undefine($$) {
my ( $hash, $name ) = @_;
# 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";
}
}
return undef;
}
###################################
sub RESIDENTS_Notify($$) {
my ( $hash, $dev ) = @_;
my $devName = $dev->{NAME};
my $hashName = $hash->{NAME};
my $hashName_attr;
# process child notifies
if ( $devName ne $hashName ) {
my @registeredRoommates =
split( /,/, $hash->{ROOMMATES} )
if ( defined( $hash->{ROOMMATES} )
&& $hash->{ROOMMATES} ne "" );
my @registeredGuests =
split( /,/, $hash->{GUESTS} )
if ( defined( $hash->{GUESTS} )
&& $hash->{GUESTS} ne "" );
# process only registered ROOMMATE or GUEST devices
if ( ( @registeredRoommates && $devName ~~ @registeredRoommates )
|| ( @registeredGuests && $devName ~~ @registeredGuests ) )
{
return
if ( !$dev->{CHANGED} ); # Some previous notify deleted the array.
foreach my $change ( @{ $dev->{CHANGED} } ) {
# state changed
if ( $change !~ /:/ || $change =~ /wayhome:/ ) {
Log3 $hash, 4,
"RESIDENTS "
. $hashName . ": "
. $devName
. ": notify about change to $change";
RESIDENTS_UpdateReadings($hash);
}
# activity
if ( $change !~ /:/ ) {
# get user realname
my $realnamesrc = (
defined( $attr{$devName}{rr_realname} )
&& $attr{$devName}{rr_realname} ne ""
? $attr{$devName}{rr_realname}
: "group"
);
my $realname = (
defined( $attr{$devName}{$realnamesrc} )
&& $attr{$devName}{$realnamesrc} ne ""
? $attr{$devName}{$realnamesrc}
: $devName
);
# update statistics
readingsBeginUpdate($hash);
readingsBulkUpdate( $hash, "lastActivity", $change );
readingsBulkUpdate( $hash, "lastActivityBy", $realname );
readingsEndUpdate( $hash, 1 );
}
}
}
}
return;
}
###################################
sub RESIDENTS_Set($@) {
my ( $hash, @a ) = @_;
my $name = $hash->{NAME};
my $state =
( defined( $hash->{READINGS}{state}{VAL} ) )
? $hash->{READINGS}{state}{VAL}
: "initialized";
my $roommates = ( $hash->{ROOMMATES} ? $hash->{ROOMMATES} : "" );
my $guests = ( $hash->{GUESTS} ? $hash->{GUESTS} : "" );
Log3 $name, 5, "RESIDENTS $name: called function RESIDENTS_Set()";
return "No Argument given" if ( !defined( $a[1] ) );
# states
my $states = (
defined( $attr{$name}{rgr_states} ) ? $attr{$name}{rgr_states}
: (
defined( $attr{$name}{rgr_showAllStates} )
&& $attr{$name}{rgr_showAllStates} == 1
? "home,gotosleep,asleep,awoken,absent,gone"
: "home,gotosleep,absent,gone"
)
);
$states = $state . "," . $states
if ( $state ne "initialized" && $states !~ /$state/ );
my $usage =
"Unknown argument " . $a[1] . ", choose one of addRoommate addGuest";
$usage .= " state:$states";
$usage .= " removeRoommate:" . $roommates if ( $roommates ne "" );
$usage .= " removeGuest:" . $guests if ( $guests ne "" );
# 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 "gone" )
{
my $newstate;
my $presence = "absent";
# 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 "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];
}
Log3 $name, 2, "RESIDENTS set $name " . $newstate;
# loop through every roommate
if ( defined( $hash->{ROOMMATES} )
&& $hash->{ROOMMATES} ne "" )
{
my @registeredRoommates =
split( /,/, $hash->{ROOMMATES} );
foreach my $roommate (@registeredRoommates) {
if ( defined( $defs{$roommate} )
&& $defs{$roommate}{READINGS}{state} ne $newstate )
{
fhem "set $roommate silentSet state $newstate";
}
}
}
# loop through every guest
if ( defined( $hash->{GUESTS} )
&& $hash->{GUESTS} ne "" )
{
$newstate = "none" if ( $newstate eq "gone" );
my @registeredGuests =
split( /,/, $hash->{GUESTS} );
foreach my $guest (@registeredGuests) {
if ( defined( $defs{$guest} )
&& $defs{$guest}{READINGS}{state}{VAL} ne "none"
&& $defs{$guest}{READINGS}{state}{VAL} ne $newstate )
{
fhem "set $guest silentSet state $newstate";
}
}
}
}
# addRoommate
elsif ( $a[1] eq "addRoommate" ) {
Log3 $name, 2, "RESIDENTS set $name " . $a[1] . " " . $a[2]
if ( defined( $a[2] ) );
my $rr_name;
my $rr_name_attr;
if ( $a[2] ne "" ) {
$rr_name = "rr_" . $a[2];
# define roommate
if ( !defined( $defs{$rr_name} ) ) {
fhem( "define " . $rr_name . " ROOMMATE " . $name );
if ( defined( $defs{$rr_name} ) ) {
fhem "set $rr_name silentSet state home";
Log3 $name, 3,
"RESIDENTS $name: created new device $rr_name";
}
}
else {
return "Can't create, device $rr_name already existing.";
}
}
else {
return "No Argument given, choose one of name ";
}
}
# removeRoommate
elsif ( $a[1] eq "removeRoommate" ) {
Log3 $name, 2, "RESIDENTS set $name " . $a[1] . " " . $a[2]
if ( defined( $a[2] ) );
if ( $a[2] ne "" ) {
my $rr_name = $a[2];
# delete roommate
if ( defined( $defs{$rr_name} ) ) {
Log3 $name, 3, "RESIDENTS $name: deleted device $rr_name"
if fhem( "delete " . $rr_name );
}
}
else {
return "No Argument given, choose one of name ";
}
}
# addGuest
elsif ( $a[1] eq "addGuest" ) {
Log3 $name, 2, "RESIDENTS set $name " . $a[1] . " " . $a[2]
if ( defined( $a[2] ) );
my $rg_name;
my $rg_name_attr;
if ( $a[2] ne "" ) {
$rg_name = "rg_" . $a[2];
# define guest
if ( !defined( $defs{$rg_name} ) ) {
fhem( "define " . $rg_name . " GUEST " . $name );
if ( defined( $defs{$rg_name} ) ) {
fhem "set $rg_name silentSet state none";
Log3 $name, 3,
"RESIDENTS $name: created new device $rg_name";
}
}
else {
return "Can't create, device $rg_name already existing.";
}
}
else {
return "No Argument given, choose one of name ";
}
}
# removeGuest
elsif ( $a[1] eq "removeGuest" ) {
Log3 $name, 2, "RESIDENTS set $name " . $a[1] . " " . $a[2]
if ( defined( $a[2] ) );
if ( $a[2] ne "" ) {
my $rg_name = $a[2];
# delete guest
if ( defined( $defs{$rg_name} ) ) {
Log3 $name, 3, "RESIDENTS $name: deleted device $rg_name"
if fhem( "delete " . $rg_name );
}
}
else {
return "No Argument given, choose one of name ";
}
}
# register
elsif ( $a[1] eq "register" ) {
if ( defined( $a[2] ) && $a[2] ne "" ) {
return "No such device " . $a[2]
if ( !defined( $defs{ $a[2] } ) );
# ROOMMATE
if ( $defs{ $a[2] }{TYPE} eq "ROOMMATE" ) {
Log3 $name, 4, "RESIDENTS $name: " . $a[2] . " registered";
# update readings
$roommates .= ( $roommates eq "" ? $a[2] : "," . $a[2] )
if ( $roommates !~ /$a[2]/ );
$hash->{ROOMMATES} = $roommates;
}
# GUEST
elsif ( $defs{ $a[2] }{TYPE} eq "GUEST" ) {
Log3 $name, 4, "RESIDENTS $name: " . $a[2] . " registered";
# update readings
$guests .= ( $guests eq "" ? $a[2] : "," . $a[2] )
if ( $guests !~ /$a[2]/ );
$hash->{GUESTS} = $guests;
}
# unsupported
else {
return "Device type is not supported.";
}
}
else {
return "No Argument given, choose one of ROOMMATE GUEST ";
}
}
# unregister
elsif ( $a[1] eq "unregister" ) {
if ( defined( $a[2] ) && $a[2] ne "" ) {
return "No such device " . $a[2]
if ( !defined( $defs{ $a[2] } ) );
# ROOMMATE
if ( $defs{ $a[2] }{TYPE} eq "ROOMMATE" ) {
Log3 $name, 4, "RESIDENTS $name: " . $a[2] . " unregistered";
# update readings
my $replace = "," . $a[2];
$roommates =~ s/$replace//g;
$replace = $a[2] . ",";
$roommates =~ s/^$replace//g;
$roommates =~ s/^$a[2]//g;
$hash->{ROOMMATES} = $roommates;
}
# GUEST
elsif ( $defs{ $a[2] }{TYPE} eq "GUEST" ) {
Log3 $name, 4, "RESIDENTS $name: " . $a[2] . " unregistered";
# update readings
my $replace = "," . $a[2];
$guests =~ s/$replace//g;
$replace = $a[2] . ",";
$guests =~ s/^$replace//g;
$guests =~ s/^$a[2]//g;
$hash->{GUESTS} = $guests;
}
# unsupported
else {
return "Device type is not supported.";
}
}
else {
return "No Argument given, choose one of ROOMMATE GUEST ";
}
RESIDENTS_UpdateReadings($hash);
}
# return usage hint
else {
return $usage;
}
return undef;
}
############################################################################################################
#
# Begin of helper functions
#
############################################################################################################
sub RESIDENTS_UpdateReadings (@) {
my ($hash) = @_;
my $state =
( defined $hash->{READINGS}{state}{VAL} ) ? $hash->{READINGS}{state}{VAL}
: ( defined $hash->{STATE} ) ? $hash->{STATE}
: "undefined";
my $name = $hash->{NAME};
my $state_home = 0;
my $state_gotosleep = 0;
my $state_asleep = 0;
my $state_awoken = 0;
my $state_absent = 0;
my $state_gone = 0;
my $state_total = 0;
my $state_totalPresent = 0;
my $state_totalAbsent = 0;
my $state_totalGuests = 0;
my $state_guestDev = 0;
my $wayhome = 0;
my $newstate;
my $presence = "absent";
my @registeredRoommates =
split( /,/, $hash->{ROOMMATES} )
if ( defined( $hash->{ROOMMATES} )
&& $hash->{ROOMMATES} ne "" );
my @registeredGuests =
split( /,/, $hash->{GUESTS} )
if ( defined( $hash->{GUESTS} )
&& $hash->{GUESTS} ne "" );
# count child states for ROOMMATE devices
foreach my $roommate (@registeredRoommates) {
$state_total++;
if ( defined( $defs{$roommate}{READINGS}{state}{VAL} ) ) {
if ( $defs{$roommate}{READINGS}{state}{VAL} eq "home" ) {
$state_home++;
$state_totalPresent++;
}
if ( $defs{$roommate}{READINGS}{state}{VAL} eq "gotosleep" ) {
$state_gotosleep++;
$state_totalPresent++;
}
if ( $defs{$roommate}{READINGS}{state}{VAL} eq "asleep" ) {
$state_asleep++;
$state_totalPresent++;
}
if ( $defs{$roommate}{READINGS}{state}{VAL} eq "awoken" ) {
$state_awoken++;
$state_totalPresent++;
}
if ( $defs{$roommate}{READINGS}{state}{VAL} eq "absent" ) {
$state_absent++;
$state_totalAbsent++;
}
if ( $defs{$roommate}{READINGS}{state}{VAL} eq "gone" ) {
$state_gone++;
$state_totalAbsent++;
}
}
if ( defined( $defs{$roommate}{READINGS}{wayhome}{VAL} ) ) {
$wayhome += $defs{$roommate}{READINGS}{wayhome}{VAL};
}
}
# count child states for GUEST devices
foreach my $guest (@registeredGuests) {
$state_guestDev++;
if ( defined( $defs{$guest}{READINGS}{state}{VAL} ) ) {
if ( $defs{$guest}{READINGS}{state}{VAL} eq "home" ) {
$state_home++;
$state_totalPresent++;
$state_totalGuests++;
$state_total++;
}
if ( $defs{$guest}{READINGS}{state}{VAL} eq "gotosleep" ) {
$state_gotosleep++;
$state_totalPresent++;
$state_totalGuests++;
$state_total++;
}
if ( $defs{$guest}{READINGS}{state}{VAL} eq "asleep" ) {
$state_asleep++;
$state_totalPresent++;
$state_totalGuests++;
$state_total++;
}
if ( $defs{$guest}{READINGS}{state}{VAL} eq "awoken" ) {
$state_awoken++;
$state_totalPresent++;
$state_totalGuests++;
$state_total++;
}
if ( $defs{$guest}{READINGS}{state}{VAL} eq "absent" ) {
$state_absent++;
$state_totalAbsent++;
$state_totalGuests++;
$state_total++;
}
}
if ( defined( $defs{$guest}{READINGS}{wayhome}{VAL} ) ) {
$wayhome += $defs{$guest}{READINGS}{wayhome}{VAL};
}
}
# update counter
readingsBeginUpdate($hash);
readingsBulkUpdate( $hash, "residentsTotal", $state_total )
if ( !defined( $hash->{READINGS}{residentsTotal}{VAL} )
|| $hash->{READINGS}{residentsTotal}{VAL} ne $state_total );
readingsBulkUpdate( $hash, "residentsGuests", $state_totalGuests )
if ( !defined( $hash->{READINGS}{residentsGuests}{VAL} )
|| $hash->{READINGS}{residentsGuests}{VAL} ne $state_totalGuests );
readingsBulkUpdate( $hash, "residentsTotalPresent", $state_totalPresent )
if ( !defined( $hash->{READINGS}{residentsTotalPresent}{VAL} )
|| $hash->{READINGS}{residentsTotalPresent}{VAL} ne
$state_totalPresent );
readingsBulkUpdate( $hash, "residentsTotalAbsent", $state_totalAbsent )
if ( !defined( $hash->{READINGS}{residentsTotalAbsent}{VAL} )
|| $hash->{READINGS}{residentsTotalAbsent}{VAL} ne $state_totalAbsent );
readingsBulkUpdate( $hash, "residentsHome", $state_home )
if ( !defined( $hash->{READINGS}{residentsHome}{VAL} )
|| $hash->{READINGS}{residentsHome}{VAL} ne $state_home );
readingsBulkUpdate( $hash, "residentsGotosleep", $state_gotosleep )
if ( !defined( $hash->{READINGS}{residentsGotosleep}{VAL} )
|| $hash->{READINGS}{residentsGotosleep}{VAL} ne $state_gotosleep );
readingsBulkUpdate( $hash, "residentsAsleep", $state_asleep )
if ( !defined( $hash->{READINGS}{residentsAsleep}{VAL} )
|| $hash->{READINGS}{residentsAsleep}{VAL} ne $state_asleep );
readingsBulkUpdate( $hash, "residentsAwoken", $state_awoken )
if ( !defined( $hash->{READINGS}{residentsAwoken}{VAL} )
|| $hash->{READINGS}{residentsAwoken}{VAL} ne $state_awoken );
readingsBulkUpdate( $hash, "residentsAbsent", $state_absent )
if ( !defined( $hash->{READINGS}{residentsAbsent}{VAL} )
|| $hash->{READINGS}{residentsAbsent}{VAL} ne $state_absent );
readingsBulkUpdate( $hash, "residentsGone", $state_gone )
if ( !defined( $hash->{READINGS}{residentsGone}{VAL} )
|| $hash->{READINGS}{residentsGone}{VAL} ne $state_gone );
readingsBulkUpdate( $hash, "residentsTotalWayhome", $wayhome )
if ( !defined( $hash->{READINGS}{residentsTotalWayhome}{VAL} )
|| $hash->{READINGS}{residentsTotalWayhome}{VAL} ne $wayhome );
#
# state calculation
#
# gotosleep
if ( $state_home == 0
&& $state_gotosleep > 0
&& $state_asleep >= 0
&& $state_awoken == 0 )
{
$newstate = "gotosleep";
}
# asleep
elsif ($state_home == 0
&& $state_gotosleep == 0
&& $state_asleep > 0
&& $state_awoken == 0 )
{
$newstate = "asleep";
}
# awoken
elsif ($state_home == 0
&& $state_gotosleep >= 0
&& $state_asleep >= 0
&& $state_awoken > 0 )
{
$newstate = "awoken";
}
# general presence
elsif ($state_home > 0
|| $state_gotosleep > 0
|| $state_asleep > 0
|| $state_awoken > 0 )
{
$newstate = "home";
}
# absent
elsif ($state_absent > 0
&& $state_home == 0
&& $state_gotosleep == 0
&& $state_asleep == 0
&& $state_awoken == 0 )
{
$newstate = "absent";
}
# gone
elsif ($state_gone > 0
&& $state_absent == 0
&& $state_home == 0
&& $state_gotosleep == 0
&& $state_asleep == 0
&& $state_awoken == 0 )
{
$newstate = "gone";
}
# none
elsif ($state_totalGuests == 0
&& $state_gone == 0
&& $state_absent == 0
&& $state_home == 0
&& $state_gotosleep == 0
&& $state_asleep == 0
&& $state_awoken == 0 )
{
$newstate = "none";
}
# unspecified; this should not happen
else {
$newstate = "unspecified";
}
# calculate presence state
$presence = "present"
if ( $newstate ne "gone"
&& $newstate ne "none"
&& $newstate ne "absent" );
Log3 $name, 4,
"RESIDENTS $name: calculation result - residentsTotal:$state_total residentsGuests:$state_totalGuests residentsTotalPresent:$state_totalPresent residentsTotalAbsent:$state_totalAbsent residentsHome:$state_home residentsGotosleep:$state_gotosleep residentsAsleep:$state_asleep residentsAwoken:$state_awoken residentsAbsent:$state_absent residentsGone:$state_gone presence:$presence state:$newstate";
# safe current time
my $datetime = FmtDateTime(time);
# if state changed
if ( !defined( $hash->{READINGS}{state}{VAL} )
|| $state ne $newstate )
{
# if newstate is asleep, start sleep timer
readingsBulkUpdate( $hash, "lastSleep", $datetime )
if ( $newstate eq "asleep" );
# if prior state was asleep, update sleep statistics
if ( defined( $hash->{READINGS}{state}{VAL} )
&& $state eq "asleep" )
{
readingsBulkUpdate( $hash, "lastAwake", $datetime );
readingsBulkUpdate(
$hash,
"lastDurSleep",
RESIDENTS_TimeDiff(
$datetime, $hash->{READINGS}{lastSleep}{VAL}
)
);
readingsBulkUpdate(
$hash,
"lastDurSleep_cr",
RESIDENTS_TimeDiff(
$datetime, $hash->{READINGS}{lastSleep}{VAL}, "min"
)
);
}
readingsBulkUpdate( $hash, "lastState", $hash->{READINGS}{state}{VAL} );
readingsBulkUpdate( $hash, "state", $newstate );
}
# if presence changed
if ( !defined( $hash->{READINGS}{presence}{VAL} )
|| $hash->{READINGS}{presence}{VAL} ne $presence )
{
readingsBulkUpdate( $hash, "presence", $presence );
# update statistics
if ( $presence eq "present" ) {
readingsBulkUpdate( $hash, "lastArrival", $datetime );
# absence duration
if ( defined( $hash->{READINGS}{lastDeparture}{VAL} )
&& $hash->{READINGS}{lastDeparture}{VAL} ne "-" )
{
readingsBulkUpdate(
$hash,
"lastDurAbsence",
RESIDENTS_TimeDiff(
$datetime, $hash->{READINGS}{lastDeparture}{VAL}
)
);
readingsBulkUpdate(
$hash,
"lastDurAbsence_cr",
RESIDENTS_TimeDiff(
$datetime, $hash->{READINGS}{lastDeparture}{VAL},
"min"
)
);
}
}
else {
readingsBulkUpdate( $hash, "lastDeparture", $datetime );
# presence duration
if ( defined( $hash->{READINGS}{lastArrival}{VAL} )
&& $hash->{READINGS}{lastArrival}{VAL} ne "-" )
{
readingsBulkUpdate(
$hash,
"lastDurPresence",
RESIDENTS_TimeDiff(
$datetime, $hash->{READINGS}{lastArrival}{VAL}
)
);
readingsBulkUpdate(
$hash,
"lastDurPresence_cr",
RESIDENTS_TimeDiff(
$datetime, $hash->{READINGS}{lastArrival}{VAL},
"min"
)
);
}
}
}
readingsEndUpdate($hash, 1);
}
###################################
sub RESIDENTS_TimeDiff($$;$) {
my ( $datetimeNow, $datetimeOld, $format ) = @_;
my $timestampNow = RESIDENTS_Datetime2Timestamp($datetimeNow);
my $timestampOld = RESIDENTS_Datetime2Timestamp($datetimeOld);
my $timeDiff = $timestampNow - $timestampOld;
# return seconds
return int( $timeDiff + 0.5 ) if ( defined($format) && $format eq "sec" );
# return minutes
return int( $timeDiff / 60 + 0.5 )
if ( defined($format) && $format eq "min" );
# return human readable format
my $hours = ( $timeDiff < 3600 ? 0 : int( $timeDiff / 3600 ) );
$timeDiff -= ( $hours == 0 ? 0 : ( $hours * 3600 ) );
my $minutes = ( $timeDiff < 60 ? 0 : int( $timeDiff / 60 ) );
my $seconds = $timeDiff % 60;
$hours = "0" . $hours if ( $hours < 10 );
$minutes = "0" . $minutes if ( $minutes < 10 );
$seconds = "0" . $seconds if ( $seconds < 10 );
return "$hours:$minutes:$seconds";
}
###################################
sub RESIDENTS_Datetime2Timestamp($) {
my ($datetime) = @_;
my ( $date, $time, $y, $m, $d, $hour, $min, $sec, $timestamp );
( $date, $time ) = split( ' ', $datetime );
( $y, $m, $d ) = split( '-', $date );
( $hour, $min, $sec ) = split( ':', $time );
$m -= 01;
$timestamp = timelocal( $sec, $min, $hour, $d, $m, $y );
return $timestamp;
}
1;
=pod
=begin html
RESIDENTS
Define
define <rgr_ResidentsName> RESIDENTS
Provides a special dummy device to represent a group of individuals living at your home.
It locically combines individual states of
ROOMMATE and
GUEST devices and allows state changes for all members.
Based on the current state and other readings, you may trigger other actions within FHEM.
Example:
# Standalone
define rgr_Residents RESIDENTS
Set
set <rgr_ResidentsName> <command> [<parameter>]
Currently, the following commands are defined.
-
addGuest - creates a new GUEST device and adds it to the current RESIDENTS group. Just enter the dummy name and there you go.
-
addRoommate - creates a new ROOMMATE device and adds it to the current RESIDENTS group. Just enter the first name and there you go.
-
removeGuest - shows all GUEST members and allows to delete their dummy devices easily.
-
removeRoommate - shows all ROOMMATE members and allows to delete their dummy devices easily.
-
state home,gotosleep,asleep,awoken,absent,gone switch between states for all group members at once; see attribute rgr_states to adjust list shown in FHEMWEB
Possible states and their meaning
This module differs between 7 states:
-
home - residents are present at home and at least one of them is not asleep
-
gotosleep - present residents are on their way to bed (if they are not asleep already)
-
asleep - all present residents are currently sleeping
-
awoken - at least one resident just woke up from sleep
-
absent - no resident is currently at home but at least one will be back shortly
-
gone - all residents left home for longer period
-
none - no active member
Note: State 'none' cannot explicitly be set. Setting state to 'gone' will be handled as 'none' for GUEST member devices.
Attributes
-
rgr_showAllStates - states 'asleep' and 'awoken' are hidden by default to allow simple gotosleep process via devStateIcon; defaults to 0
-
rgr_states - list of states to be shown in FHEMWEB; separate entries by comma only and do NOT use spaces; unsupported states will lead to errors though
Generated Readings/Events:
-
lastActivity - the last state change of one of the group members
-
lastActivityBy - the realname of the last group member with changed state
-
lastArrival - timestamp of last arrival at home
-
lastAwake - timestamp of last sleep cycle end
-
lastDeparture - timestamp of last departure from home
-
lastDurAbsence - duration of last absence from home in human readable format (hours:minutes:seconds)
-
lastDurAbsence_cr - duration of last absence from home in computer readable format (minutes)
-
lastDurPresence - duration of last presence at home in human readable format (hours:minutes:seconds)
-
lastDurPresence_cr - duration of last presence at home in computer readable format (minutes)
-
lastDurSleep - duration of last sleep in human readable format (hours:minutes:seconds)
-
lastDurSleep_cr - duration of last sleep in computer readable format (minutes)
-
lastSleep - timestamp of last sleep cycle begin
-
lastState - the prior state
-
presence - reflects the home presence state, depending on value of reading 'state' (can be 'present' or 'absent')
-
residentsAbsent - number of residents with state 'absent'
-
residentsAsleep - number of residents with state 'asleep'
-
residentsAwoken - number of residents with state 'awoken'
-
residentsGone - number of residents with state 'gone'
-
residentsGotosleep - number of residents with state 'gotosleep'
-
residentsGuests - number of active guests who are currently treated as part of the residents scope
-
residentsHome - number of residents with state 'home'
-
residentsTotal - total number of all active residents despite their current state
-
residentsTotalAbsent - number of all residents who are currently underway
-
residentsTotalPresent - number of all residents who are currently at home
-
residentsTotalWayhome - number of all active residents who are currently on their way back home
-
state - reflects the current state
=end html
=begin html_DE
RESIDENTS
Define
define <rgr_ResidentsName> RESIDENTS
Stellt ein spezielles Dummy-Device bereit, um eine Gruppe von Personen zu repräsentieren, die zusammen wohnen.
Es kombiniert dabei logisch die individuellen Status von
ROOMMATE und
GUEST Devices und erlaubt den Status für alle Mitglieder zeitgleich zu ändern. Basierend auf dem aktuellen Status und anderen Readings können andere Aktionen innerhalb von FHEM angestoßen werden.
Beispiele:
# Einzeln
define rgr_Residents RESIDENTS
Set
set <rgr_ResidentsName> <command> [<parameter>]
Momentan sind die folgenden Kommandos definiert.
-
addGuest - erstellt ein neues GUEST Device und fügt es der aktuellen RESIDENTS Gruppe hinzu. Einfach den Platzhalternamen eingeben und das wars.
-
addRoommate - erstellt ein neues ROOMMATE Device und fügt es der aktuellen RESIDENTS Gruppe hinzu. Einfach den Vornamen eingeben und das wars.
-
removeGuest - zeigt alle Mitglieder vom Typ GUEST an und ermöglicht ein einfaches löschen des dazugehörigen Dummy Devices.
-
removeRoommate - zeigt alle Mitglieder vom Typ ROOMMATE an und ermöglicht ein einfaches löschen des dazugehörigen Dummy Devices.
-
state home,gotosleep,asleep,awoken,absent,gone wechselt den Status für alle Gruppenmitglieder gleichzeitig; siehe Attribut rgr_states, um die angezeigte Liste in FHEMWEB abzuändern
Mögliche Status und ihre Bedeutung
Dieses Modul unterscheidet 7 verschiedene Status:
-
home - Bewohner sind zu Hause und mindestens einer schläft nicht
-
gotosleep - alle anwesenden Bewohner sind auf dem Weg ins Bett (wenn sie nicht schon schlafen)
-
asleep - alle anwesenden Bewohner schlafen
-
awoken - mindestens einer der anwesenden Bewohner ist gerade aufgewacht
-
absent - keiner der Bewohner ist momentan zu Hause; mindestens einer ist aber in Kürze zurück
-
gone - alle Bewohner sind für längere Zeit verreist
-
none - kein Mitglied aktiv
Hinweis: Der Status 'none' kann nicht explizit gesetzt werden. Das setzen von 'gone' wird bei Mitgliedern vom Typ GUEST als 'none' behandelt.
Attribute
-
rgr_showAllStates - die Status 'asleep' und 'awoken' sind normalerweise nicht immer sichtbar, um einen einfachen Zubettgeh-Prozess über das devStateIcon Attribut zu ermöglichen; Standard ist 0
-
rgr_states - Liste aller in FHEMWEB angezeigter Status; Eintrage nur mit Komma trennen und KEINE Leerzeichen benutzen; nicht unterstützte Status führen zu Fehlern
Generierte Readings/Events:
-
lastActivity - der letzte Status Wechsel eines Gruppenmitglieds
-
lastActivityBy - der Name des Gruppenmitglieds, dessen Status zuletzt geändert wurde
-
lastArrival - Zeitstempel der letzten Ankunft zu Hause
-
lastAwake - Zeitstempel des Endes des letzten Schlafzyklus
-
lastDeparture - Zeitstempel des letzten Verlassens des Zuhauses
-
lastDurAbsence - Dauer der letzten Abwesenheit in normal lesbarem Format (Stunden:Minuten:Sekunden)
-
lastDurAbsence_cr - Dauer der letzten Abwesenheit in Computer lesbarem Format (Minuten)
-
lastDurPresence - Dauer der letzten Anwesenheit in normal lesbarem Format (Stunden:Minuten:Sekunden)
-
lastDurPresence_cr - Dauer der letzten Anwesenheit in Computer lesbarem Format (Minuten)
-
lastDurSleep - Dauer des letzten Schlafzyklus in normal lesbarem Format (Stunden:Minuten:Sekunden)
-
lastDurSleep_cr - Dauer des letzten Schlafzyklus in Computer lesbarem Format (Minuten)
-
lastSleep - Zeitstempel des Beginns des letzten Schlafzyklus
-
lastState - der vorherige Status
-
presence - gibt den zu Hause Status in Abhängigkeit des Readings 'state' wieder (kann 'present' oder 'absent' sein)
-
residentsAbsent - Anzahl der Bewohner mit Status 'absent'
-
residentsAsleep - Anzahl der Bewohner mit Status 'asleep'
-
residentsAwoken - Anzahl der Bewohner mit Status 'awoken'
-
residentsGone - Anzahl der Bewohner mit Status 'gone'
-
residentsGotosleep - Anzahl der Bewohner mit Status 'gotosleep'
-
residentsGuests - Anzahl der aktiven Gäste, welche momentan du den Bewohnern dazugezählt werden
-
residentsHome - Anzahl der Bewohner mit Status 'home'
-
residentsTotal - Summe aller aktiven Bewohner unabhängig von ihrem aktuellen Status
-
residentsTotalAbsent - Summe aller aktiven Bewohner, die unterwegs sind
-
residentsTotalPresent - Summe aller aktiven Bewohner, die momentan zu Hause sind
-
residentsTotalWayhome - Summe aller aktiven Bewohner, die momentan auf dem Weg zurück nach Hause sind
-
state - gibt den aktuellen Status wieder
=end html_DE
=cut