diff --git a/fhem/FHEM/20_GUEST.pm b/fhem/FHEM/20_GUEST.pm index 6f82e85e1..c1d20dc4a 100644 --- a/fhem/FHEM/20_GUEST.pm +++ b/fhem/FHEM/20_GUEST.pm @@ -35,6 +35,7 @@ require RESIDENTStk; sub GUEST_Set($@); sub GUEST_Define($$); sub GUEST_Notify($$); +sub GUEST_Attr(@); sub GUEST_Undefine($$); ################################### @@ -46,9 +47,10 @@ sub GUEST_Initialize($) { $hash->{SetFn} = "GUEST_Set"; $hash->{DefFn} = "GUEST_Define"; $hash->{NotifyFn} = "GUEST_Notify"; + $hash->{AttrFn} = "GUEST_Attr"; $hash->{UndefFn} = "GUEST_Undefine"; $hash->{AttrList} = -"rg_locationHome rg_locationWayhome rg_locationUnderway rg_autoGoneAfter:12,16,24,26,28,30,36,48,60 rg_showAllStates:0,1 rg_realname:group,alias rg_states:multiple-strict,home,gotosleep,asleep,awoken,absent,gone rg_locations rg_moods rg_moodDefault rg_moodSleepy rg_noDuration:0,1 rg_wakeupDevice rg_geofenceUUIDs rg_presenceDevices " +"disable:1,0 rg_locationHome rg_locationWayhome rg_locationUnderway rg_autoGoneAfter:0,12,16,24,26,28,30,36,48,60 rg_showAllStates:0,1 rg_realname:group,alias rg_states:multiple-strict,home,gotosleep,asleep,awoken,absent,gone rg_locations rg_moods rg_moodDefault rg_moodSleepy rg_noDuration:0,1 rg_wakeupDevice rg_geofenceUUIDs rg_presenceDevices rg_lang:EN,DE " . $readingFnAttributes; } @@ -82,6 +84,8 @@ sub GUEST_Define($$) { # set default settings on first define if ( $init_done && !defined( $hash->{OLDDEF} ) ) { + GUEST_Attr( "init", $name, "rg_lang" ); + my $aliasname = $name; $aliasname =~ s/^rg_//; $attr{$name}{alias} = $aliasname; @@ -123,12 +127,86 @@ sub GUEST_Define($$) { return undef; } +################################### +sub GUEST_Attr(@) { + my ( $cmd, $name, $attribute, $value ) = @_; + my $hash = $defs{$name}; + my $prefix = "rg_"; + return unless ($init_done); + + Log3 $name, 5, "GUEST $name: called function GUEST_Attr()"; + + if ( $attribute eq "disable" ) { + if ( $value and $value == 1 ) { + $hash->{STATE} = "disabled"; + GUEST_StopInternalTimers($hash); + } + elsif ( $cmd eq "del" or !$value ) { + evalStateFormat($hash); + GUEST_StartInternalTimers( $hash, 1 ); + } + } + + elsif ( $attribute eq $prefix . "autoGoneAfter" ) { + if ($value) { + GUEST_AutoGone($hash); + } + elsif ( !$value ) { + delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); + RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); + } + } + + elsif ( $attribute eq $prefix . "noDuration" ) { + if ($value) { + delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); + RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); + } + elsif ( !$value ) { + GUEST_DurationTimer($hash); + } + } + + elsif ( $attribute eq $prefix . "lang" ) { + my $lang = + $cmd eq "set" ? uc($value) : AttrVal( "global", "language", "EN" ); + + # for initial define, ensure fallback to EN + $lang = "EN" + if ( $cmd eq "init" && $lang !~ /^EN|DE$/i ); + + if ( $lang eq "DE" ) { + $attr{$name}{devStateIcon} = +'.*anwesend:user_available:absent .*abwesend:user_away:home .*keiner:control_building_empty:home .*bettfertig:scene_toilet:asleep .*schläft:scene_sleeping:awoken .*aufgestanden:scene_sleeping_alternat:home .*:user_unknown:home'; + $attr{$name}{eventMap} = +"home:anwesend absent:abwesend none:keiner gotosleep:bettfertig asleep:schläft awoken:aufgestanden"; + $attr{$name}{widgetOverride} = + "state:anwesend,bettfertig,abwesend,keiner"; + } + elsif ( $lang eq "EN" ) { + $attr{$name}{devStateIcon} = +'.*home:user_available:absent .*absent:user_away:home .*none:control_building_empty:home .*gotosleep:scene_toilet:asleep .*asleep:scene_sleeping:awoken .*awoken:scene_sleeping_alternat:home .*:user_unknown:home'; + delete $attr{$name}{eventMap} + if ( defined( $attr{$name}{eventMap} ) ); + delete $attr{$name}{widgetOverride} + if ( defined( $attr{$name}{widgetOverride} ) ); + } + else { + return "Unsupported language $lang"; + } + + evalStateFormat($hash); + } + + return if ( IsDisabled($name) ); + return; +} + ################################### sub GUEST_Undefine($$) { my ( $hash, $name ) = @_; - RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); - RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); + GUEST_StopInternalTimers($hash); if ( defined( $hash->{RESIDENTGROUPS} ) ) { my $old = $hash->{RESIDENTGROUPS}; @@ -150,6 +228,7 @@ sub GUEST_Notify($$) { my ( $hash, $dev ) = @_; my $devName = $dev->{NAME}; my $hashName = $hash->{NAME}; + return if ( IsDisabled($hashName) or IsDisabled($devName) ); # process global:INITIALIZED if ( $dev->{NAME} eq "global" @@ -282,6 +361,8 @@ sub GUEST_Set($@) { my $location = ReadingsVal( $name, "location", "undefined" ); my $silent = 0; + return if ( IsDisabled($name) ); + Log3 $name, 5, "GUEST $name: called function GUEST_Set()"; return "No Argument given" if ( !defined( $a[1] ) ); @@ -652,6 +733,7 @@ sub GUEST_Set($@) { GUEST_AutoGone($hash); } elsif ( $state eq "absent" ) { + delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); } } @@ -884,20 +966,19 @@ sub GUEST_Set($@) { ################################### sub GUEST_AutoGone($;$) { my ( $mHash, @a ) = @_; - my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; - my $name = $hash->{NAME}; + my $hash = ( $mHash->{HASH} ) ? $mHash->{HASH} : $mHash; + my $name = $hash->{NAME}; + my $autoGoneAfter = AttrVal( $hash->{NAME}, "rg_autoGoneAfter", 16 ); + delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); + return if ( IsDisabled($name) ); + if ( ReadingsVal( $name, "state", "home" ) eq "absent" ) { my ( $date, $time, $y, $m, $d, $hour, $min, $sec, $timestamp, $timeDiff ); my $timestampNow = gettimeofday(); - my $timeout = ( - defined( $attr{$name}{rg_autoGoneAfter} ) - ? $attr{$name}{rg_autoGoneAfter} - : "16" - ); ( $date, $time ) = split( ' ', $hash->{READINGS}{state}{TIME} ); ( $y, $m, $d ) = split( '-', $date ); @@ -906,13 +987,14 @@ sub GUEST_AutoGone($;$) { $timestamp = timelocal( $sec, $min, $hour, $d, $m, $y ); $timeDiff = $timestampNow - $timestamp; - if ( $timeDiff >= $timeout * 3600 ) { + if ( $timeDiff >= $autoGoneAfter * 3600 ) { Log3 $name, 3, "GUEST $name: AutoGone timer changed state to 'gone'"; GUEST_Set( $hash, $name, "silentSet", "state", "gone" ); } else { - my $runtime = $timestamp + $timeout * 3600; + my $runtime = $timestamp + $autoGoneAfter * 3600; + $hash->{AUTOGONE} = $runtime; Log3 $name, 4, "GUEST $name: AutoGone timer scheduled: $runtime"; RESIDENTStk_InternalTimer( "AutoGone", $runtime, "GUEST_AutoGone", $hash, 1 ); @@ -934,74 +1016,71 @@ sub GUEST_DurationTimer($;$) { my $durPresence = "0"; my $durAbsence = "0"; my $durSleep = "0"; + my $noDuration = AttrVal( $hash->{NAME}, "rg_noDuration", 0 ); + delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); - if ( !defined( $attr{$name}{rg_noDuration} ) - || $attr{$name}{rg_noDuration} == 0 ) + return if ( IsDisabled($name) || $noDuration ); + + # presence timer + if ( ReadingsVal( $name, "presence", "absent" ) eq "present" + && ReadingsVal( $name, "lastArrival", "-" ) ne "-" ) { - - # presence timer - if ( ReadingsVal( $name, "presence", "absent" ) eq "present" - && ReadingsVal( $name, "lastArrival", "-" ) ne "-" ) - { - $durPresence = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastArrival", "" ) ); - } - - # absence timer - if ( ReadingsVal( $name, "presence", "present" ) eq "absent" - && ReadingsVal( $name, "lastDeparture", "-" ) ne "-" ) - { - $durAbsence = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastDeparture", "" ) ); - } - - # sleep timer - if ( ReadingsVal( $name, "state", "home" ) eq "asleep" - && ReadingsVal( $name, "lastSleep", "-" ) ne "-" ) - { - $durSleep = - $timestampNow - - time_str2num( ReadingsVal( $name, "lastSleep", "" ) ); - } - - my $durPresence_hr = - ( $durPresence > 0 ) - ? RESIDENTStk_sec2time($durPresence) - : "00:00:00"; - my $durPresence_cr = - ( $durPresence > 60 ) ? int( $durPresence / 60 + 0.5 ) : 0; - my $durAbsence_hr = - ( $durAbsence > 0 ) ? RESIDENTStk_sec2time($durAbsence) : "00:00:00"; - my $durAbsence_cr = - ( $durAbsence > 60 ) ? int( $durAbsence / 60 + 0.5 ) : 0; - my $durSleep_hr = - ( $durSleep > 0 ) ? RESIDENTStk_sec2time($durSleep) : "00:00:00"; - my $durSleep_cr = ( $durSleep > 60 ) ? int( $durSleep / 60 + 0.5 ) : 0; - - readingsBeginUpdate($hash) if ( !$silent ); - readingsBulkUpdate( $hash, "durTimerPresence_cr", $durPresence_cr ) - if ( ReadingsVal( $name, "durTimerPresence_cr", "" ) ne - $durPresence_cr ); - readingsBulkUpdate( $hash, "durTimerPresence", $durPresence_hr ) - if ( - ReadingsVal( $name, "durTimerPresence", "" ) ne $durPresence_hr ); - readingsBulkUpdate( $hash, "durTimerAbsence_cr", $durAbsence_cr ) - if ( - ReadingsVal( $name, "durTimerAbsence_cr", "" ) ne $durAbsence_cr ); - readingsBulkUpdate( $hash, "durTimerAbsence", $durAbsence_hr ) - if ( ReadingsVal( $name, "durTimerAbsence", "" ) ne $durAbsence_hr ); - readingsBulkUpdate( $hash, "durTimerSleep_cr", $durSleep_cr ) - if ( ReadingsVal( $name, "durTimerSleep_cr", "" ) ne $durSleep_cr ); - readingsBulkUpdate( $hash, "durTimerSleep", $durSleep_hr ) - if ( ReadingsVal( $name, "durTimerSleep", "" ) ne $durSleep_hr ); - readingsEndUpdate( $hash, 1 ) if ( !$silent ); + $durPresence = + $timestampNow - + time_str2num( ReadingsVal( $name, "lastArrival", "" ) ); } - RESIDENTStk_InternalTimer( "DurationTimer", $timestampNow + 60, + # absence timer + if ( ReadingsVal( $name, "presence", "present" ) eq "absent" + && ReadingsVal( $name, "lastDeparture", "-" ) ne "-" ) + { + $durAbsence = + $timestampNow - + time_str2num( ReadingsVal( $name, "lastDeparture", "" ) ); + } + + # sleep timer + if ( ReadingsVal( $name, "state", "home" ) eq "asleep" + && ReadingsVal( $name, "lastSleep", "-" ) ne "-" ) + { + $durSleep = + $timestampNow - time_str2num( ReadingsVal( $name, "lastSleep", "" ) ); + } + + my $durPresence_hr = + ( $durPresence > 0 ) + ? RESIDENTStk_sec2time($durPresence) + : "00:00:00"; + my $durPresence_cr = + ( $durPresence > 60 ) ? int( $durPresence / 60 + 0.5 ) : 0; + my $durAbsence_hr = + ( $durAbsence > 0 ) ? RESIDENTStk_sec2time($durAbsence) : "00:00:00"; + my $durAbsence_cr = + ( $durAbsence > 60 ) ? int( $durAbsence / 60 + 0.5 ) : 0; + my $durSleep_hr = + ( $durSleep > 0 ) ? RESIDENTStk_sec2time($durSleep) : "00:00:00"; + my $durSleep_cr = ( $durSleep > 60 ) ? int( $durSleep / 60 + 0.5 ) : 0; + + readingsBeginUpdate($hash) if ( !$silent ); + readingsBulkUpdate( $hash, "durTimerPresence_cr", $durPresence_cr ) + if ( ReadingsVal( $name, "durTimerPresence_cr", "" ) ne $durPresence_cr ); + readingsBulkUpdate( $hash, "durTimerPresence", $durPresence_hr ) + if ( ReadingsVal( $name, "durTimerPresence", "" ) ne $durPresence_hr ); + readingsBulkUpdate( $hash, "durTimerAbsence_cr", $durAbsence_cr ) + if ( ReadingsVal( $name, "durTimerAbsence_cr", "" ) ne $durAbsence_cr ); + readingsBulkUpdate( $hash, "durTimerAbsence", $durAbsence_hr ) + if ( ReadingsVal( $name, "durTimerAbsence", "" ) ne $durAbsence_hr ); + readingsBulkUpdate( $hash, "durTimerSleep_cr", $durSleep_cr ) + if ( ReadingsVal( $name, "durTimerSleep_cr", "" ) ne $durSleep_cr ); + readingsBulkUpdate( $hash, "durTimerSleep", $durSleep_hr ) + if ( ReadingsVal( $name, "durTimerSleep", "" ) ne $durSleep_hr ); + readingsEndUpdate( $hash, 1 ) if ( !$silent ); + + $hash->{DURATIONTIMER} = $timestampNow + 60; + + RESIDENTStk_InternalTimer( "DurationTimer", $hash->{DURATIONTIMER}, "GUEST_DurationTimer", $hash, 1 ) if ( $state ne "none" ); @@ -1203,6 +1282,17 @@ sub GUEST_StartInternalTimers($$) { GUEST_DurationTimer($hash); } +################################### +sub GUEST_StopInternalTimers($) { + my ($hash) = @_; + + delete $hash->{AUTOGONE} if ( $hash->{AUTOGONE} ); + delete $hash->{DURATIONTIMER} if ( $hash->{DURATIONTIMER} ); + + RESIDENTStk_RemoveInternalTimer( "AutoGone", $hash ); + RESIDENTStk_RemoveInternalTimer( "DurationTimer", $hash ); +} + 1; =pod @@ -1358,6 +1448,9 @@ sub GUEST_StartInternalTimers($$) {
  • rg_geofenceUUIDs - comma separated list of device UUIDs updating their location via GEOFANCY. Avoids necessity for additional notify/DOIF/watchdog devices and can make GEOFANCY attribute devAlias obsolete. (using more than one UUID/device might not be a good idea as location my leap)
  • +
  • + rg_lang - overwrite global language setting; helps to set device attributes to translate FHEMWEB display text +
  • rg_locationHome - locations matching these will be treated as being at home; first entry reflects default value to be used with state correlation; separate entries by space; defaults to 'home'
  • @@ -1380,7 +1473,7 @@ sub GUEST_StartInternalTimers($$) { rg_moods - list of moods to be shown in FHEMWEB; separate entries by comma only and do NOT use spaces
  • - rg_noDuration - may be used to disable duration timer calculation (see readings durTimer*) + rg_noDuration - may be used to disable continuous, non-event driven duration timer calculation (see readings durTimer*)
  • rg_passPresenceTo - synchronize presence state with other GUEST or GUEST devices; separte devices by space @@ -1655,6 +1748,9 @@ sub GUEST_StartInternalTimers($$) {
  • rg_geofenceUUIDs - Mit Komma getrennte Liste von Geräte UUIDs, die ihren Standort über GEOFANCY aktualisieren. Vermeidet zusätzliche notify/DOIF/watchdog Geräte und kann als Ersatz für das GEOFANCY attribute devAlias dienen. (hier ehr als eine UUID/Device zu hinterlegen ist eher keine gute Idee da die Lokation dann womöglich anfängt zu springen)
  • +
  • + rg_lang - überschreibt globale Spracheinstellung; hilft beim setzen von Device Attributen, um FHEMWEB Anzeigetext zu übersetzen +
  • rg_locationHome - hiermit übereinstimmende Lokationen werden als zu Hause gewertet; der erste Eintrag wird für das Zusammenspiel bei Statusänderungen benutzt; mehrere Einträge durch Leerzeichen trennen; Standard ist 'home'
  • @@ -1677,7 +1773,7 @@ sub GUEST_StartInternalTimers($$) { rg_moods - Liste von Stimmungen, wie sie in FHEMWEB angezeigt werden sollen; mehrere Einträge nur durch Komma trennen und KEINE Leerzeichen verwenden
  • - rg_noDuration - deaktiviert die Berechnung der Zeitspannen (siehe Readings durTimer*) + rg_noDuration - deaktiviert die kontinuierliche, nicht Event-basierte Berechnung der Zeitspannen (siehe Readings durTimer*)
  • rg_passPresenceTo - synchronisiere die Anwesenheit mit anderen GUEST oder ROOMMATE Devices; mehrere Devices durch Leerzeichen trennen