############################################################################### # # Developed with VSCodium and richterger perl plugin # # (c) 2018-2023 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) # All rights reserved # # Special thanks goes to: # - Bernd (Cluni) this module is based on the logic of his script "Rollladensteuerung für HM/ROLLO inkl. Abschattung und Komfortfunktionen in Perl" (https://forum.fhem.de/index.php/topic,73964.0.html) # - Beta-User for many tests, many suggestions and good discussions # - pc1246 write english commandref # - FunkOdyssey commandref style # - sledge fix many typo in commandref # - many User that use with modul and report bugs # - Christoph (christoph.kaiser.in) Patch that expand RegEx for Window Events # - Julian (Loredo) expand Residents Events for new Residents functions # - Christoph (Christoph Morrison) for fix Commandref, many suggestions and good discussions # # # This script 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 # any later version. # # The GNU General Public License can be found at # http://www.gnu.org/copyleft/gpl.html. # A copy is found in the textfile GPL.txt and important notices to the license # from the author is found in LICENSE.txt distributed with these scripts. # # This script 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. # # # $Id$ # ############################################################################### ### Notizen # !!!!! - Innerhalb einer Shutterschleife kein CommandAttr verwenden. Bring Fehler!!! Kommen Raumnamen in die Shutterliste !!!!!! # package main; use strict; use warnings; use utf8; sub ascAPIget { my ( $getCommand, $shutterDev, $value ) = @_; return ShuttersControl_ascAPIget( $getCommand, $shutterDev, $value ); } sub ascAPIset { my ( $setCommand, $shutterDev, $value ) = @_; return ShuttersControl_ascAPIset( $setCommand, $shutterDev, $value ); } ## unserer packagename package FHEM::Automation::ShuttersControl; use strict; use warnings; use POSIX qw(strftime); use utf8; use Encode; use FHEM::Meta; use GPUtils qw(GP_Import GP_Export); use Data::Dumper; #only for Debugging use Date::Parse; use experimental qw( switch ); use FHEM::Automation::ShuttersControl::Shutters; use FHEM::Automation::ShuttersControl::Dev; use FHEM::Automation::ShuttersControl::Shading qw (CheckASC_ConditionsForShadingFn); use FHEM::Automation::ShuttersControl::EventProcessingFunctions qw (:ALL); use FHEM::Automation::ShuttersControl::Helper qw (:ALL); # try to use JSON::MaybeXS wrapper # for chance of better performance + open code eval { require JSON::MaybeXS; import JSON::MaybeXS qw( decode_json encode_json ); 1; } or do { # try to use JSON wrapper # for chance of better performance eval { # JSON preference order local $ENV{PERL_JSON_BACKEND} = 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); require JSON; import JSON qw( decode_json encode_json ); 1; } or do { # In rare cases, Cpanel::JSON::XS may # be installed but JSON|JSON::MaybeXS not ... eval { require Cpanel::JSON::XS; import Cpanel::JSON::XS qw(decode_json encode_json); 1; } or do { # In rare cases, JSON::XS may # be installed but JSON not ... eval { require JSON::XS; import JSON::XS qw(decode_json encode_json); 1; } or do { # Fallback to built-in JSON which SHOULD # be available since 5.014 ... eval { require JSON::PP; import JSON::PP qw(decode_json encode_json); 1; } or do { # Fallback to JSON::backportPP in really rare cases require JSON::backportPP; import JSON::backportPP qw(decode_json encode_json); 1; }; }; }; }; }; ## Import der FHEM Funktionen #-- Run before package compilation BEGIN { # Import from main context GP_Import( qw( defs modules attr readingFnAttributes ::AttrVal ::ReadingsVal ::IsDisabled ::deviceEvents ::init_done ::addToDevAttrList ::addToAttrList ::delFromDevAttrList ::delFromAttrList ::gettimeofday ::InternalTimer ::RemoveInternalTimer) ); #-- Export to main context with different name GP_Export( qw( ascAPIget ascAPIset DevStateIcon ) ); } ## Die Attributsliste welche an die Rolläden verteilt wird. Zusammen mit Default Werten ##no critic our %userAttrList = ( 'ASC_Mode_Up:absent,always,off,home' => '-', 'ASC_Mode_Down:absent,always,off,home' => '-', 'ASC_Up:time,astro,brightness,roommate' => '-', 'ASC_Down:time,astro,brightness,roommate' => '-', 'ASC_AutoAstroModeMorning:REAL,CIVIL,NAUTIC,ASTRONOMIC,HORIZON' => '-', 'ASC_AutoAstroModeMorningHorizon:-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9' => '-', 'ASC_AutoAstroModeEvening:REAL,CIVIL,NAUTIC,ASTRONOMIC,HORIZON' => '-', 'ASC_AutoAstroModeEveningHorizon:-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9' => '-', 'ASC_Open_Pos:0,10,20,30,40,50,60,70,80,90,100' => [ '', 0, 100 ], 'ASC_Closed_Pos:0,10,20,30,40,50,60,70,80,90,100' => [ '', 100, 0 ], 'ASC_Sleep_Pos:0,10,20,30,40,50,60,70,80,90,100' => [ '', -1, -1 ], 'ASC_Pos_Reading' => [ '', 'position', 'pct' ], 'ASC_Time_Up_Early' => '-', 'ASC_Time_Up_Late' => '-', 'ASC_Time_Up_WE_Holiday' => '-', 'ASC_Time_Down_Early' => '-', 'ASC_Time_Down_Late' => '-', 'ASC_PrivacyUpValue_beforeDayOpen' => '-', 'ASC_PrivacyDownValue_beforeNightClose' => '-', 'ASC_PrivacyUp_Pos' => [ '', 50, 50 ], 'ASC_PrivacyDown_Pos' => [ '', 50, 50 ], 'ASC_TempSensor' => '-', 'ASC_Ventilate_Window_Open:on,off' => '-', 'ASC_LockOut:soft,hard,off' => '-', 'ASC_LockOut_Cmd:inhibit,blocked,protection' => '-', 'ASC_BlockingTime_afterManual' => '-', 'ASC_BlockingTime_beforeNightClose' => '-', 'ASC_BlockingTime_beforeDayOpen' => '-', 'ASC_BrightnessSensor' => '-', 'ASC_Shading_Pos:10,20,30,40,50,60,70,80,90,100' => [ '', 80, 20 ], 'ASC_Shading_Mode:absent,always,off,home' => '-', 'ASC_Shading_InOutAzimuth' => '-', 'ASC_Shading_StateChange_SunnyCloudy' => '-', 'ASC_Shading_MinMax_Elevation' => '-', 'ASC_Shading_Min_OutsideTemperature' => '-', 'ASC_Shading_WaitingPeriod' => '-', 'ASC_Shading_BetweenTheTime' => '-', 'ASC_Drive_Delay' => '-', 'ASC_Drive_DelayStart' => '-', 'ASC_Shutter_IdleDetection' => '-', 'ASC_WindowRec' => '-', 'ASC_WindowRec_subType:twostate,threestate' => '-', 'ASC_WindowRec_PosAfterDayClosed:open,lastManual' => '-', 'ASC_ShuttersPlace:window,terrace,awning,EG_window' => '-', 'ASC_Ventilate_Pos:10,20,30,40,50,60,70,80,90,100' => [ '', 70, 30 ], 'ASC_ComfortOpen_Pos:0,10,20,30,40,50,60,70,80,90,100' => [ '', 20, 80 ], 'ASC_GuestRoom:on,off' => '-', 'ASC_Antifreeze:off,soft,hard,am,pm' => '-', 'ASC_Antifreeze_Pos:5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100' => [ '', 85, 15 ], 'ASC_Partymode:on,off' => '-', 'ASC_Roommate_Device' => '-', 'ASC_Roommate_Reading' => '-', 'ASC_Self_Defense_Mode:absent,gone,off' => '-', 'ASC_Self_Defense_AbsentDelay' => '-', 'ASC_WiggleValue' => '-', 'ASC_WindParameters' => '-', 'ASC_DriveUpMaxDuration' => '-', 'ASC_WindProtection:on,off' => '-', 'ASC_RainProtection:on,off' => '-', 'ASC_ExternalTrigger' => '-', 'ASC_Adv:on,off' => '-', 'ASC_CommandTemplate' => '-', 'ASC_SlatPosCmd_SlatDevice' => '-', ); ## 2 Objekte werden erstellt our $shutters = FHEM::Automation::ShuttersControl::Shutters->new(); our $ascDev = FHEM::Automation::ShuttersControl::Dev->new(); ##use critic my %posSetCmds = ( ZWave => 'dim', Siro => 'pct', CUL_HM => 'pct', ROLLO => 'pct', Shelly => 'pct', HMCCUDEV => 'pct', SOMFY => 'position', tahoma => 'dim', KLF200Node => 'pct', DUOFERN => 'position', HM485 => 'level', SELVECommeo => 'position', SELVE => 'position', EnOcean => 'position', ); sub ascAPIget { my $getCommand = shift; my $shutterDev = shift; my $value = shift; my $getter = 'get' . $getCommand; if ( defined($value) && $value ) { $shutters->setShuttersDev($shutterDev); return $shutters->$getter($value); } elsif ( defined($shutterDev) && $shutterDev ) { $shutters->setShuttersDev($shutterDev); return $shutters->$getter; } else { return $ascDev->$getter; } return; } sub ascAPIset { my $setCommand = shift; my $shutterDev = shift; my $value = shift; my $setter = 'set' . $setCommand; if ( defined($shutterDev) && $shutterDev && defined($value) ) { $shutters->setShuttersDev($shutterDev); $shutters->$setter($value); } return; } sub Define { my $hash = shift // return; my $aArg = shift // return; my $version; return $@ unless ( FHEM::Meta::SetInternals($hash) ); $version = FHEM::Meta::Get( $hash, 'version' ); our $VERSION = $version; return 'only one AutoShuttersControl instance allowed' if ( ::devspec2array('TYPE=AutoShuttersControl') > 1 ) ; # es wird geprüft ob bereits eine Instanz unseres Modules existiert,wenn ja wird abgebrochen return 'too few parameters: define ShuttersControl' if ( scalar( @{$aArg} ) != 2 ); my $name = shift @$aArg; $hash->{MID} = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' ; # eine Ein Eindeutige ID für interne FHEM Belange / nicht weiter wichtig $hash->{VERSION} = version->parse($VERSION)->normal; $hash->{NOTIFYDEV} = 'global,' . $name; # Liste aller Devices auf deren Events gehört werden sollen #$hash->{shutters} = $shutters; #$hash->{ascDev} = $ascDev; $ascDev->setName($name); ::readingsSingleUpdate( $hash, 'state', 'please set attribute ASC with value 1 or 2 in all auto controlled shutter devices and then execute \'set DEVICENAME scanForShutters\'', 1 ); ::CommandAttr( undef, $name . ' room ASC' ) if ( ::AttrVal( $name, 'room', 'none' ) eq 'none' ); ::CommandAttr( undef, $name . ' icon fts_shutter_automatic' ) if ( ::AttrVal( $name, 'icon', 'none' ) eq 'none' ); ::CommandAttr( undef, $name . ' devStateIcon { ShuttersControl_DevStateIcon($name) }' ) if ( ::AttrVal( $name, 'devStateIcon', 'none' ) eq 'none' ); ::addToAttrList( 'ASC:0,1,2', 'AutoShuttersControl' ); ::Log3( $name, 3, "AutoShuttersControl ($name) - defined" ); $modules{AutoShuttersControl}{defptr}{ $hash->{MID} } = $hash; return; } sub Undef { my $hash = shift; my $name = shift; UserAttributs_Readings_ForShutters( $hash, 'del' ) ; # es sollen alle Attribute und Readings in den Rolläden Devices gelöscht werden welche vom Modul angelegt wurden ::delFromAttrList('ASC:0,1,2'); delete( $modules{AutoShuttersControl}{defptr}{ $hash->{MID} } ); ::Log3( $name, 3, "AutoShuttersControl ($name) - delete device $name" ); return; } sub Delete { my $hash = shift; my $name = shift; RemoveShuttersTimer($hash); return; } sub Shutdown { my $hash = shift; RemoveShuttersTimer($hash); return; } sub Notify { my $hash = shift // return; my $dev = shift // return; my $name = $hash->{NAME}; my $devname = $dev->{NAME}; my $devtype = $dev->{TYPE}; my $events = ::deviceEvents( $dev, 1 ); return if ( !$events ); ::Log3( $name, 4, "AutoShuttersControl ($name) - Devname: " . $devname . " Name: " . $name . " Notify: " . Dumper $events); # mit Dumper if ( ( grep { /^DEFINED.$name$/ } @{$events} && $devname eq 'global' && $::init_done ) || ( grep { /^INITIALIZED$/ } @{$events} or grep { /^REREADCFG$/ } @{$events} or grep { /^MODIFIED.$name$/ } @{$events} ) && $devname eq 'global' ) { ::readingsSingleUpdate( $hash, 'partyMode', 'off', 0 ) if ( $ascDev->getPartyMode eq 'none' ); ::readingsSingleUpdate( $hash, 'hardLockOut', 'off', 0 ) if ( $ascDev->getHardLockOut eq 'none' ); ::readingsSingleUpdate( $hash, 'sunriseTimeWeHoliday', 'off', 0 ) if ( $ascDev->getSunriseTimeWeHoliday eq 'none' ); ::readingsSingleUpdate( $hash, 'selfDefense', 'off', 0 ) if ( $ascDev->getSelfDefense eq 'none' ); ::readingsSingleUpdate( $hash, 'controlShading', 'off', 0 ) if ( $ascDev->getAutoShuttersControlShading eq 'none' ); ::readingsSingleUpdate( $hash, 'ascEnable', 'on', 0 ) if ( $ascDev->getASCenable eq 'none' ); ::CommandAttr( undef, $name . ' devStateIcon { ShuttersControl_DevStateIcon($name) }' ) if ( ::AttrVal( $name, 'devStateIcon', '{ ShuttersControl_DevStateIcon($name) }' ) ne '{ ShuttersControl_DevStateIcon($name) }' ); ::CommandDeleteAttr( undef, $name . ' event-on-change-reading' ) if ( ::AttrVal( $name, 'event-on-change-reading', 'none' ) ne 'none' ); ::CommandDeleteAttr( undef, $name . ' event-on-update-reading' ) if ( ::AttrVal( $name, 'event-on-update-reading', 'none' ) ne 'none' ); # Ist der Event ein globaler und passt zum Rest der Abfrage oben wird nach neuen Rolläden Devices gescannt und eine Liste im Rolladenmodul sortiert nach Raum generiert ShuttersDeviceScan($hash) if ( ::ReadingsVal( $name, 'userAttrList', 'none' ) ne 'none' ); } return if ( ref( $hash->{helper}{shuttersList} ) ne 'ARRAY' || scalar( @{ $hash->{helper}{shuttersList} } ) == 0 ); my $posReading = $shutters->getPosCmd; if ( $devname eq $name ) { if ( grep { /^userAttrList:.rolled.out$/ } @{$events} ) { if ( scalar( @{ $hash->{helper}{shuttersList} } ) > 0 ) { WriteReadingsShuttersList($hash); UserAttributs_Readings_ForShutters( $hash, 'add' ); ::InternalTimer( ::gettimeofday() + 3, 'FHEM::Automation::ShuttersControl::RenewSunRiseSetShuttersTimer', $hash ); ::InternalTimer( ::gettimeofday() + 5, 'FHEM::Automation::ShuttersControl::AutoSearchTwilightDev', $hash ); ::InternalTimer( ::gettimeofday() + 5, sub() { ::CommandSet( undef, $name . ' controlShading on' ); }, $hash ) if ( ::ReadingsVal( $name, 'controlShading', 'off' ) ne 'off' ); } } elsif ( grep { /^partyMode:.off$/ } @{$events} ) { EventProcessingPartyMode($hash); } elsif ( grep { /^sunriseTimeWeHoliday:.(on|off)$/ } @{$events} ) { RenewSunRiseSetShuttersTimer($hash); } } elsif ( $devname eq "global" ) { # Kommt ein globales Event und beinhaltet folgende Syntax wird die Funktion zur Verarbeitung aufgerufen ::Log3( $name, 4, "AutoShuttersControl ($name) - ShuttersControl: Devicename eq global" ); if ( grep { /^(ATTR|DELETEATTR)\s(.*ASC_Time_Up_WE_Holiday|.*ASC_Up|.*ASC_Down|.*ASC_AutoAstroModeMorning|.*ASC_AutoAstroModeMorningHorizon|.*ASC_AutoAstroModeEvening|.*ASC_AutoAstroModeEveningHorizon|.*ASC_Time_Up_Early|.*ASC_Time_Up_Late|.*ASC_Time_Down_Early|.*ASC_Time_Down_Late|.*ASC_autoAstroModeMorning|.*ASC_autoAstroModeMorningHorizon|.*ASC_PrivacyDownValue_beforeNightClose|.*ASC_PrivacyUpValue_beforeDayOpen|.*ASC_autoAstroModeEvening|.*ASC_autoAstroModeEveningHorizon|.*ASC_Roommate_Device|.*ASC_WindowRec|.*ASC_residentsDev|.*ASC_rainSensor|.*ASC_windSensor|.*ASC_tempSensor|.*ASC_BrightnessSensor|.*ASC_twilightDevice|.*ASC_ExternalTrigger|.*ASC_Shading_StateChange_SunnyCloudy|.*ASC_TempSensor|.*ASC_Shading_Mode)(\s.*|$) /x } @{$events} ) { ::Log3( $name, 4, "AutoShuttersControl ($name) - ShuttersControl: Event Attr or Deleteattr Time and Astro" ); EventProcessingGeneral( $hash, undef, join( ' ', @{$events} ) ); } } elsif ( grep { /^($posReading):\s\d{1,3}(\.\d{1,3})?$/x } @{$events} ) { ASC_Debug( 'Notify: ' . ' ASC_Pos_Reading Event vom Rollo ' . $devname . ' wurde erkannt ' . ' - RECEIVED EVENT: ' . Dumper $events); EventProcessingShutters( $hash, $devname, join( ' ', @{$events} ) ); } else { EventProcessingGeneral( $hash, $devname, join( ' ', @{$events} ) ) ; # bei allen anderen Events wird die entsprechende Funktion zur Verarbeitung aufgerufen } return; } sub Set { my $hash = shift // return; my $aArg = shift // return; my $name = shift @$aArg; my $cmd = shift @$aArg // return qq{"set $name" needs at least one argument}; given ($cmd) { when ('renewAllTimer') { return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); RenewSunRiseSetShuttersTimer($hash); } when ('renewTimer') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); CreateSunRiseSetShuttersTimer( $hash, $aArg->[0] ); } when ('scanForShutters') { return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); ShuttersDeviceScan($hash); } when ('createNewNotifyDev') { return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); CreateNewNotifyDev($hash); } when ('partyMode') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); ::readingsSingleUpdate( $hash, $cmd, $aArg->[0], 1 ) if ( $aArg->[0] ne ::ReadingsVal( $name, 'partyMode', 0 ) ); } when ('hardLockOut') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); ::readingsSingleUpdate( $hash, $cmd, $aArg->[0], 1 ); HardewareBlockForShutters( $hash, $aArg->[0] ); } when ('sunriseTimeWeHoliday') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); ::readingsSingleUpdate( $hash, $cmd, $aArg->[0], 1 ); } when ('controlShading') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); my $response = CheckASC_ConditionsForShadingFn( $hash, $aArg->[0] ); ::readingsSingleUpdate( $hash, $cmd, ( $aArg->[0] eq 'off' ? $aArg->[0] : ( $response eq 'none' ? $aArg->[0] : $response ) ), 1 ); } when ('selfDefense') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); ::readingsSingleUpdate( $hash, $cmd, $aArg->[0], 1 ); } when ('ascEnable') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); ::readingsSingleUpdate( $hash, $cmd, $aArg->[0], 1 ); } when ('advDriveDown') { return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); EventProcessingAdvShuttersClose($hash); } when ('shutterASCenableToggle') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); ::readingsSingleUpdate( $defs{ $aArg->[0] }, 'ASC_Enable', ( ::ReadingsVal( $aArg->[0], 'ASC_Enable', 'off' ) eq 'on' ? 'off' : 'on' ), 1 ); } when ('wiggle') { return "usage: $cmd" if ( scalar( @{$aArg} ) > 1 ); ( $aArg->[0] eq 'all' ? wiggleAll($hash) : wiggle( $hash, $aArg->[0] ) ); } default { my $list = 'scanForShutters:noArg'; $list .= ' renewAllTimer:noArg advDriveDown:noArg partyMode:on,off hardLockOut:on,off sunriseTimeWeHoliday:on,off controlShading:on,off selfDefense:on,off ascEnable:on,off wiggle:all,' . join( ',', @{ $hash->{helper}{shuttersList} } ) . ' shutterASCenableToggle:' . join( ',', @{ $hash->{helper}{shuttersList} } ) . ' renewTimer:' . join( ',', @{ $hash->{helper}{shuttersList} } ) if ( ::ReadingsVal( $name, 'userAttrList', 'none' ) eq 'rolled out' && defined( $hash->{helper}{shuttersList} ) && scalar( @{ $hash->{helper}{shuttersList} } ) > 0 ); $list .= ' createNewNotifyDev:noArg' if ( ::ReadingsVal( $name, 'userAttrList', 'none' ) eq 'rolled out' && ::AttrVal( $name, 'ASC_expert', 0 ) == 1 ); return "Unknown argument $cmd,choose one of $list"; } } return; } sub Get { my $hash = shift // return; my $aArg = shift // return; my $name = shift @$aArg // return; my $cmd = shift @$aArg // return qq{"get $name" needs at least one argument}; if ( lc $cmd eq 'shownotifydevsinformations' ) { return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); my $ret = GetMonitoredDevs($hash); return $ret; } else { my $list = ""; $list .= " showNotifyDevsInformations:noArg" if ( ::ReadingsVal( $name, 'userAttrList', 'none' ) eq 'rolled out' && ::AttrVal( $name, 'ASC_expert', 0 ) == 1 ); return "Unknown argument $cmd,choose one of $list"; } } sub ShuttersDeviceScan { my $hash = shift; my $name = $hash->{NAME}; delete $hash->{helper}{shuttersList}; my @list; @list = ::devspec2array('ASC=[1-2]'); ::CommandDeleteReading( undef, $name . ' .*_nextAstroTimeEvent' ); if ( scalar(@list) == 0 ) { ::readingsBeginUpdate($hash); ::readingsBulkUpdate( $hash, 'userAttrList', 'none' ); ::readingsBulkUpdate( $hash, 'state', 'no shutters found' ); ::readingsEndUpdate( $hash, 1 ); return; } my $shuttersList = ''; for my $shuttersDev (@list) { push( @{ $hash->{helper}{shuttersList} }, $shuttersDev ) ; ## einem Hash wird ein Array zugewiesen welches die Liste der erkannten Rollos beinhaltet $shutters->setShuttersDev($shuttersDev); #### Ab hier können temporäre Änderungen der Attribute gesetzt werden #### Gleichlautende Attribute wo lediglich die Parameter geändert werden sollen müssen hier gelöscht und die Parameter in der Funktion renewSetSunriseSunsetTimer gesetzt werden, #### vorher empfiehlt es sich die dort vergebenen Parameter aus zu lesen um sie dann hier wieder neu zu setzen. Dazu wird das shutters Objekt um einen Eintrag #### 'AttrUpdateChanges' erweitert if ( ::ReadingsVal( $shuttersDev, '.ASC_AttrUpdateChanges_' . $hash->{VERSION}, 0 ) == 0 ) { $shutters->setAttrUpdateChanges( 'ASC_ShuttersPlace', ::AttrVal( $shuttersDev, 'ASC_ShuttersPlace', 'none' ) ); ::delFromDevAttrList( $shuttersDev, 'ASC_ShuttersPlace' ); # $shutters->setAttrUpdateChanges( 'ASC_Down', # ::AttrVal( $shuttersDev, 'ASC_Down', 'none' ) ); # ::delFromDevAttrList( $shuttersDev, 'ASC_Down' ); # $shutters->setAttrUpdateChanges( 'ASC_Self_Defense_Mode', # ::AttrVal( $shuttersDev, 'ASC_Self_Defense_Mode', 'none' ) ); # ::delFromDevAttrList( $shuttersDev, 'ASC_Self_Defense_Mode' ); # $shutters->setAttrUpdateChanges( 'ASC_Self_Defense_Exclude', # ::AttrVal( $shuttersDev, 'ASC_Self_Defense_Exclude', 'none' ) ); # ::delFromDevAttrList( $shuttersDev, 'ASC_Self_Defense_Exclude' ); } #### #### $shuttersList = $shuttersList . ',' . $shuttersDev; $shutters->setLastManPos( $shutters->getStatus ); $shutters->setLastPos( $shutters->getStatus ); $shutters->setDelayCmd('none'); $shutters->setNoDelay(0); $shutters->setSelfDefenseAbsent( 0, 0 ); $shutters->setPosSetCmd( $posSetCmds{ $defs{$shuttersDev}->{TYPE} } ); $shutters->setShadingStatus( ( $shutters->getStatus != $shutters->getShadingPos ? 'out' : 'in' ) ); # $shutters->setShadingLastStatus( # ( $shutters->getStatus != $shutters->getShadingPos ? 'in' : 'out' ) # ); $shutters->setPushBrightnessInArray( $shutters->getBrightness ); ::readingsSingleUpdate( $defs{$shuttersDev}, 'ASC_Enable', 'on', 0 ) if ( ::ReadingsVal( $shuttersDev, 'ASC_Enable', 'none' ) eq 'none' ); if ( $shutters->getIsDay ) { $shutters->setSunrise(1); $shutters->setSunset(0); } else { $shutters->setSunrise(0); $shutters->setSunset(1); } } $hash->{NOTIFYDEV} = "global," . $name . $shuttersList; if ( $ascDev->getMonitoredDevs ne 'none' ) { $hash->{monitoredDevs} = eval { decode_json( $ascDev->getMonitoredDevs ) }; my $notifyDevString = $hash->{NOTIFYDEV}; while ( my $shuttersDev = each %{ $hash->{monitoredDevs} } ) { $notifyDevString .= ',' . $shuttersDev; } $hash->{NOTIFYDEV} = $notifyDevString; } ::readingsSingleUpdate( $hash, 'userAttrList', 'rolled out', 1 ); return; } ## Die Funktion schreibt in das Moduldevice Readings welche Rolläden in welchen Räumen erfasst wurden. sub WriteReadingsShuttersList { my $hash = shift; my $name = $hash->{NAME}; ::CommandDeleteReading( undef, $name . ' room_.*' ); ::readingsBeginUpdate($hash); for my $shuttersDev ( @{ $hash->{helper}{shuttersList} } ) { ::readingsBulkUpdate( $hash, 'room_' . ::makeReadingName( ::AttrVal( $shuttersDev, 'room', 'unsorted' ) ), ::ReadingsVal( $name, 'room_' . ::makeReadingName( ::AttrVal( $shuttersDev, 'room', 'unsorted' ) ), '' ) . ',' . $shuttersDev ) if ( ::ReadingsVal( $name, 'room_' . ::makeReadingName( ::AttrVal( $shuttersDev, 'room', 'unsorted' ) ), 'none' ) ne 'none' ); ::readingsBulkUpdate( $hash, 'room_' . ::makeReadingName( ::AttrVal( $shuttersDev, 'room', 'unsorted' ) ), $shuttersDev ) if ( ::ReadingsVal( $name, 'room_' . ::makeReadingName( ::AttrVal( $shuttersDev, 'room', 'unsorted' ) ), 'none' ) eq 'none' ); } ::readingsBulkUpdate( $hash, 'state', 'active' ); ::readingsEndUpdate( $hash, 0 ); return; } sub UserAttributs_Readings_ForShutters { my $hash = shift; my $cmd = shift; my $name = $hash->{NAME}; while ( my ( $attrib, $attribValue ) = each %{userAttrList} ) { for my $shuttersDev ( @{ $hash->{helper}{shuttersList} } ) { ::addToDevAttrList( $shuttersDev, $attrib, 'AutoShuttersControl' ) ; ## fhem.pl bietet eine Funktion um ein userAttr Attribut zu befüllen. Wir schreiben also in den Attribut userAttr alle unsere Attribute rein. Pro Rolladen immer ein Attribut pro Durchlauf ## Danach werden die Attribute die im userAttr stehen gesetzt und mit default Werten befüllt ## CommandAttr hat nicht funktioniert. Führte zu Problemen ## https://github.com/LeonGaultier/fhem-AutoShuttersControl/commit/e33d3cc7815031b087736c1054b98c57817e7083 if ( $cmd eq 'add' ) { if ( ref($attribValue) ne 'ARRAY' ) { $attr{$shuttersDev}{ ( split( ':', $attrib ) )[0] } = $attribValue if ( !defined( $attr{$shuttersDev}{ ( split( ':', $attrib ) )[0] } ) && $attribValue ne '-' ); } else { $attr{$shuttersDev}{ ( split( ':', $attrib ) )[0] } = $attribValue->[ ::AttrVal( $shuttersDev, 'ASC', 2 ) ] if ( !defined( $attr{$shuttersDev}{ ( split( ':', $attrib ) )[0] } ) && $attrib eq 'ASC_Pos_Reading' ); } ### associatedWith damit man sieht das der Rollladen mit einem ASC Device verbunden ist my $associatedString = ::ReadingsVal( $shuttersDev, 'associatedWith', 'none' ); if ( $associatedString ne 'none' ) { my %hash; %hash = map { ( $_ => 1 ) } split( ',', "$associatedString,$name" ); ::readingsSingleUpdate( $defs{$shuttersDev}, 'associatedWith', join( ',', sort keys %hash ), 0 ); } else { ::readingsSingleUpdate( $defs{$shuttersDev}, 'associatedWith', $name, 0 ); } ####################################### } ## Oder das Attribut wird wieder gelöscht. elsif ( $cmd eq 'del' ) { $shutters->setShuttersDev($shuttersDev); ::RemoveInternalTimer( $shutters->getInTimerFuncHash ); ::CommandDeleteReading( undef, $shuttersDev . ' .?(ASC)_.*' ); ::CommandDeleteAttr( undef, $shuttersDev . ' ASC' ); ::delFromDevAttrList( $shuttersDev, $attrib ); ### associatedWith wird wieder entfernt my $associatedString = ::ReadingsVal( $shuttersDev, 'associatedWith', 'none' ); my %hash; %hash = map { ( $_ => 1 ) } grep { " $name " !~ m{ $shuttersDev }xms } split( ',', "$associatedString,$name" ); if ( keys %hash > 1 ) { ::readingsSingleUpdate( $defs{$shuttersDev}, 'associatedWith', join( ',', sort keys %hash ), 0 ); } else { ::CommandDeleteReading( undef, $shuttersDev . ' associatedWith' ); } ################################### } } } return; } ## Fügt dem NOTIFYDEV Hash weitere Devices hinzu sub AddNotifyDev { ### Beispielaufruf: AddNotifyDev( $hash, $3, $1, $2 ) if ( $3 ne 'none' ); my ( $hash, $attrVal, $shuttersDev, $shuttersAttr ) = @_; $attrVal = ( split( ':', $attrVal ) )[0]; my ( $key, $value ) = split( ':', ( split( ' ', $attrVal ) )[0], 2 ) ; ## Wir versuchen die Device Attribute anders zu setzen. device=DEVICE reading=READING $attrVal = $key; my $name = $hash->{NAME}; my $notifyDev = $hash->{NOTIFYDEV}; $notifyDev = '' if ( !$notifyDev ); my %hash; %hash = map { ( $_ => 1 ) } split( ',', "$notifyDev,$attrVal" ); $hash->{NOTIFYDEV} = join( ',', sort keys %hash ); my @devs = split( ',', $attrVal ); for my $dev (@devs) { $hash->{monitoredDevs}{$dev}{$shuttersDev} = $shuttersAttr; } ::readingsSingleUpdate( $hash, '.monitoredDevs', eval { encode_json( $hash->{monitoredDevs} ) }, 0 ); return; } ## entfernt aus dem NOTIFYDEV Hash Devices welche als Wert in Attributen steckten sub DeleteNotifyDev { my $hash = shift; my $shuttersDev = shift; my $shuttersAttr = shift; my $name = $hash->{NAME}; my $notifyDevs = ExtractNotifyDevFromEvent( $hash, $shuttersDev, $shuttersAttr ); for my $notifyDev ( keys( %{$notifyDevs} ) ) { ::Log3( $name, 4, "AutoShuttersControl ($name) - DeleteNotifyDev - NotifyDev: " . $notifyDev ); delete $hash->{monitoredDevs}{$notifyDev}{$shuttersDev}; if ( !keys %{ $hash->{monitoredDevs}{$notifyDev} } ) { delete $hash->{monitoredDevs}{$notifyDev}; my $notifyDevString = $hash->{NOTIFYDEV}; $notifyDevString = '' if ( !$notifyDevString ); my %hash; %hash = map { ( $_ => 1 ) } grep { " $notifyDev " !~ m{ $_ }xms } split( ',', "$notifyDevString,$notifyDev" ); $hash->{NOTIFYDEV} = join( ',', sort keys %hash ); } } ::readingsSingleUpdate( $hash, '.monitoredDevs', eval { encode_json( $hash->{monitoredDevs} ) }, 0 ); return; } # Sub für das Zusammensetzen der Rolläden Steuerbefehle sub ShuttersCommandSet { my $hash = shift; my $shuttersDev = shift; my $posValue = shift; my $name = $hash->{NAME}; $shutters->setShuttersDev($shuttersDev); if ( ( CheckIfShuttersWindowRecOpen($shuttersDev) == 2 && $shutters->getShuttersPlace eq 'terrace' && !$shutters->getQueryShuttersPos($posValue) ) || ( ( $shutters->getPartyMode eq 'on' && $ascDev->getPartyMode eq 'on' ) || ( CheckIfShuttersWindowRecOpen($shuttersDev) == 2 && $shutters->getSubTyp eq 'threestate' && ( $ascDev->getAutoShuttersControlComfort eq 'off' || $shutters->getComfortOpenPos != $posValue ) && $shutters->getVentilateOpen eq 'on' && $shutters->getShuttersPlace eq 'window' && $shutters->getLockOut ne 'off' ) || ( CheckIfShuttersWindowRecOpen($shuttersDev) == 2 && $shutters->getSubTyp eq 'threestate' && $ascDev->getAutoShuttersControlComfort eq 'on' && $shutters->getVentilateOpen eq 'off' && $shutters->getShuttersPlace eq 'window' && $shutters->getLockOut ne 'off' ) || ( CheckIfShuttersWindowRecOpen($shuttersDev) == 2 && ( $shutters->getLockOut eq 'soft' || $shutters->getLockOut eq 'hard' ) && !$shutters->getQueryShuttersPos($posValue) ) || ( CheckIfShuttersWindowRecOpen($shuttersDev) == 2 && $shutters->getShuttersPlace eq 'terrace' && !$shutters->getQueryShuttersPos($posValue) && ( $shutters->getLockOut eq 'soft' || $shutters->getLockOut eq 'hard' ) ) || ( $shutters->getRainProtectionStatus eq 'protected' || $shutters->getWindProtectionStatus eq 'protected' ) ) ) { $shutters->setDelayCmd($posValue); $ascDev->setDelayCmdReading; $shutters->setNoDelay(0); ::Log3( $name, 4, "AutoShuttersControl ($name) - ShuttersCommandSet in Delay" ); ASC_Debug( 'FnShuttersCommandSet: ' . $shutters->getShuttersDev . ' - Die Fahrt wird zurückgestellt. Grund kann ein geöffnetes Fenster sein oder ein aktivierter Party Modus' ); } else { $shutters->setDriveCmd($posValue); $ascDev->setLastPosReading; ::Log3( $name, 4, "AutoShuttersControl ($name) - ShuttersCommandSet setDriveCmd wird aufgerufen" ); ASC_Debug( 'FnShuttersCommandSet: ' . $shutters->getShuttersDev . ' - Das Rollo wird gefahren. Kein Partymodus aktiv und das zugordnete Fenster ist entweder nicht offen oder keine Terassentür' ); } return; } ## Sub welche die InternalTimer nach entsprechenden Sunset oder Sunrise zusammen stellt sub CreateSunRiseSetShuttersTimer { my $hash = shift; my $shuttersDev = shift // return ::Log3( $hash->{NAME}, 1, "AutoShuttersControl ($hash->{NAME}) - Error in function CreateSunRiseSetShuttersTimer. No shuttersDev given" ); my $name = $hash->{NAME}; my $shuttersDevHash = $defs{$shuttersDev} // return ::Log3( $hash->{NAME}, 1, "AutoShuttersControl ($hash->{NAME}) - Error in function CreateSunRiseSetShuttersTimer. No shuttersDevHash found for device name $shuttersDev" ); my %funcHash; $shutters->setShuttersDev($shuttersDev); return if ( ::IsDisabled($name) ); my $shuttersSunriseUnixtime = ShuttersSunrise( $shuttersDev, 'unix' ) + 1; my $shuttersSunsetUnixtime = ShuttersSunset( $shuttersDev, 'unix' ) + 1; $shutters->setSunriseUnixTime($shuttersSunriseUnixtime); $shutters->setSunsetUnixTime($shuttersSunsetUnixtime); ## In jedem Rolladen werden die errechneten Zeiten hinterlegt,es sei denn das autoShuttersControlEvening/Morning auf off steht ::readingsBeginUpdate($shuttersDevHash); ::readingsBulkUpdate( $shuttersDevHash, 'ASC_Time_DriveDown', ( $ascDev->getAutoShuttersControlEvening eq 'on' ? ( $shutters->getDown eq 'roommate' ? 'roommate only' : strftime( "%d.%m.%Y - %H:%M", localtime($shuttersSunsetUnixtime) ) ) : 'AutoShuttersControl off' ) ); ::readingsBulkUpdate( $shuttersDevHash, 'ASC_Time_DriveUp', ( $ascDev->getAutoShuttersControlMorning eq 'on' ? ( $shutters->getUp eq 'roommate' ? 'roommate only' : strftime( "%d.%m.%Y - %H:%M", localtime($shuttersSunriseUnixtime) ) ) : 'AutoShuttersControl off' ) ); ::readingsEndUpdate( $shuttersDevHash, 0 ); ::readingsBeginUpdate($hash); ::readingsBulkUpdateIfChanged( $hash, $shuttersDev . '_nextAstroTimeEvent', ( $shuttersSunriseUnixtime < $shuttersSunsetUnixtime ? strftime( "%d.%m.%Y - %H:%M", localtime($shuttersSunriseUnixtime) ) : strftime( "%d.%m.%Y - %H:%M", localtime($shuttersSunsetUnixtime) ) ) ); ::readingsEndUpdate( $hash, 1 ); ::RemoveInternalTimer( $shutters->getInTimerFuncHash ) if ( defined( $shutters->getInTimerFuncHash ) ); ## Setzt den Privacy Modus für die Sichtschutzfahrt auf den Status 0 ## 1 bedeutet das Privacy Timer aktiviert wurde, 2 beudet das er im privacy ist ## also das Rollo in privacy Position steht und VOR der endgültigen Nacht oder Tagfahrt $shutters->setPrivacyUpStatus(0) if ( !defined( $shutters->getPrivacyUpStatus ) ); $shutters->setPrivacyDownStatus(0) if ( !defined( $shutters->getPrivacyDownStatus ) ); ## Abfrage für die Sichtschutzfahrt am Morgen vor dem eigentlichen kompletten öffnen if ( $shutters->getPrivacyUpTime > 0 ) { $shuttersSunriseUnixtime = PrivacyUpTime( $shuttersDevHash, $shuttersSunriseUnixtime ); } else { ::CommandDeleteReading( undef, $shuttersDev . ' ASC_Time_PrivacyDriveUp' ) if ( ::ReadingsVal( $shuttersDev, 'ASC_Time_PrivacyDriveUp', 'none' ) ne 'none' ); } ## Abfrage für die Sichtschutzfahrt am Abend vor dem eigentlichen kompletten schließen if ( $shutters->getPrivacyDownTime > 0 ) { $shuttersSunsetUnixtime = PrivacyDownTime( $shuttersDevHash, $shuttersSunsetUnixtime ); } else { ::CommandDeleteReading( undef, $shuttersDev . ' ASC_Time_PrivacyDriveDown' ) if ( ::ReadingsVal( $shuttersDev, 'ASC_Time_PrivacyDriveDown', 'none' ) ne 'none' ); } ## kleine Hilfe für InternalTimer damit ich alle benötigten Variablen an die Funktion übergeben kann welche von Internal Timer aufgerufen wird. %funcHash = ( hash => $hash, shuttersdevice => $shuttersDev, sunsettime => $shuttersSunsetUnixtime, sunrisetime => $shuttersSunriseUnixtime ); ## Ich brauche beim löschen des InternalTimer den Hash welchen ich mitgegeben habe,dieser muss gesichert werden $shutters->setInTimerFuncHash( \%funcHash ); ::InternalTimer( $shuttersSunsetUnixtime, \&SunSetShuttersAfterTimerFn, \%funcHash ); ::InternalTimer( $shuttersSunriseUnixtime, \&SunRiseShuttersAfterTimerFn, \%funcHash ); $ascDev->setStateReading('created new drive timer'); return; } ## Funktion zum neu setzen der Timer und der Readings für Sunset/Rise sub RenewSunRiseSetShuttersTimer { my $hash = shift; for my $shuttersDev ( @{ $hash->{helper}{shuttersList} } ) { my $dhash = $defs{$shuttersDev}; $shutters->setShuttersDev($shuttersDev); ::RemoveInternalTimer( $shutters->getInTimerFuncHash ); $shutters->setInTimerFuncHash(undef); CreateSunRiseSetShuttersTimer( $hash, $shuttersDev ); #### Temporär angelegt damit die neue Attributs Parameter Syntax verteilt werden kann #### Gleichlautende Attribute wo lediglich die Parameter geändert werden sollen müssen bereits in der Funktion ShuttersDeviceScan gelöscht werden #### vorher empfiehlt es sich die dort vergebenen Parameter aus zu lesen um sie dann hier wieder neu zu setzen. Dazu wird das shutters Objekt um einen Eintrag #### 'AttrUpdateChanges' erweitert if ( ( int( ::gettimeofday() ) - $::fhem_started ) < 60 and ::ReadingsVal( $shuttersDev, '.ASC_AttrUpdateChanges_' . $hash->{VERSION}, 0 ) == 0 ) { $attr{$shuttersDev}{'ASC_ShuttersPlace'} = $shutters->getAttrUpdateChanges('ASC_ShuttersPlace') if ( $shutters->getAttrUpdateChanges('ASC_ShuttersPlace') ne 'none' ); # $attr{$shuttersDev}{'ASC_Down'} = # $shutters->getAttrUpdateChanges('ASC_Down') # if ( $shutters->getAttrUpdateChanges('ASC_Down') ne 'none' ); # $attr{$shuttersDev}{'ASC_Self_Defense_Mode'} = # $shutters->getAttrUpdateChanges('ASC_Self_Defense_Mode') # if ( $shutters->getAttrUpdateChanges('ASC_Self_Defense_Mode') ne # 'none' ); # $attr{$shuttersDev}{'ASC_Self_Defense_Mode'} = 'off' # if ( # $shutters->getAttrUpdateChanges('ASC_Self_Defense_Exclude') eq # 'on' ); ::CommandDeleteReading( undef, $shuttersDev . ' .ASC_AttrUpdateChanges_.*' ) if ( ::ReadingsVal( $shuttersDev, '.ASC_AttrUpdateChanges_' . $hash->{VERSION}, 'none' ) eq 'none' ); ::readingsSingleUpdate( $dhash, '.ASC_AttrUpdateChanges_' . $hash->{VERSION}, 1, 0 ); } $attr{$shuttersDev}{ASC_BlockingTime_beforeNightClose} = ::AttrVal( $shuttersDev, 'ASC_BlockingTime_beforNightClose', 'none' ) if ( ::AttrVal( $shuttersDev, 'ASC_BlockingTime_beforNightClose', 'none' ) ne 'none' ); ::delFromDevAttrList( $shuttersDev, 'ASC_BlockingTime_beforNightClose' ); $attr{$shuttersDev}{ASC_BlockingTime_beforeDayOpen} = ::AttrVal( $shuttersDev, 'ASC_BlockingTime_beforDayOpen', 'none' ) if ( ::AttrVal( $shuttersDev, 'ASC_BlockingTime_beforDayOpen', 'none' ) ne 'none' ); ::delFromDevAttrList( $shuttersDev, 'ASC_BlockingTime_beforDayOpen' ); # # $attr{$shuttersDev}{ASC_Shading_StateChange_SunnyCloudy} = # ::AttrVal( $shuttersDev, 'ASC_Shading_StateChange_Sunny', 'none' ) . ':' # . ::AttrVal( $shuttersDev, 'ASC_Shading_StateChange_Cloudy', 'none' ) # if ( # ::AttrVal( $shuttersDev, 'ASC_Shading_StateChange_Sunny', 'none' ) ne 'none' # && ::AttrVal( $shuttersDev, 'ASC_Shading_StateChange_Cloudy', 'none' ) ne # 'none' ); # ::delFromDevAttrList( $shuttersDev, 'ASC_Shading_StateChange_Sunny' ); # ::delFromDevAttrList( $shuttersDev, 'ASC_Shading_StateChange_Cloudy' ); # # $attr{$shuttersDev}{ASC_Shading_InOutAzimuth} = # ( ::AttrVal( $shuttersDev, 'ASC_Shading_Direction', 180 ) - # ::AttrVal( $shuttersDev, 'ASC_Shading_Angle_Left', 85 ) ) # . ':' # . ( ::AttrVal( $shuttersDev, 'ASC_Shading_Direction', 180 ) + # ::AttrVal( $shuttersDev, 'ASC_Shading_Angle_Right', 85 ) ) # if ( ::AttrVal( $shuttersDev, 'ASC_Shading_Direction', 'none' ) ne 'none' # || ::AttrVal( $shuttersDev, 'ASC_Shading_Angle_Left', 'none' ) ne 'none' # || ::AttrVal( $shuttersDev, 'ASC_Shading_Angle_Right', 'none' ) ne 'none' ); # ::delFromDevAttrList( $shuttersDev, 'ASC_Shading_Direction' ); # ::delFromDevAttrList( $shuttersDev, 'ASC_Shading_Angle_Left' ); # ::delFromDevAttrList( $shuttersDev, 'ASC_Shading_Angle_Right' ); # # $attr{$shuttersDev}{ASC_PrivacyDownValue_beforeNightClose} = # ::AttrVal( $shuttersDev, 'ASC_PrivacyDownTime_beforNightClose', 'none' ) # if ( # ::AttrVal( $shuttersDev, 'ASC_PrivacyDownTime_beforNightClose', 'none' ) ne # 'none' ); # ::delFromDevAttrList( $shuttersDev, 'ASC_PrivacyDownTime_beforNightClose' ); # # ::delFromDevAttrList( $shuttersDev, 'ASC_ExternalTriggerDevice' ); } return; } ## Funktion zum hardwareseitigen setzen des lock-out oder blocking beim Rolladen selbst sub HardewareBlockForShutters { my $hash = shift; my $cmd = shift; for my $shuttersDev ( @{ $hash->{helper}{shuttersList} } ) { $shutters->setShuttersDev($shuttersDev); $shutters->setHardLockOut($cmd); } return; } ## Funktion für das wiggle aller Shutters zusammen sub wiggleAll { my $hash = shift; for my $shuttersDev ( @{ $hash->{helper}{shuttersList} } ) { wiggle( $hash, $shuttersDev ); } return; } sub wiggle { my $hash = shift; my $shuttersDev = shift; $shutters->setShuttersDev($shuttersDev); $shutters->setNoDelay(1); $shutters->setLastDrive('wiggle begin drive'); my %h = ( shuttersDev => $shutters->getShuttersDev, posValue => $shutters->getStatus, lastDrive => 'wiggle end drive', ); if ( $shutters->getShuttersPosCmdValueNegate ) { if ( $shutters->getStatus >= $shutters->getClosedPos / 2 ) { $shutters->setDriveCmd( $shutters->getStatus - $shutters->getWiggleValue ); } else { $shutters->setDriveCmd( $shutters->getStatus + $shutters->getWiggleValue ); } } else { if ( $shutters->getStatus >= $shutters->getOpenPos / 2 ) { $shutters->setDriveCmd( $shutters->getStatus - $shutters->getWiggleValue ); } else { $shutters->setDriveCmd( $shutters->getStatus + $shutters->getWiggleValue ); } } ::InternalTimer( ::gettimeofday() + 60, \&SetCmdFn, \%h ); return; } #### ## Funktion welche beim Ablaufen des Timers für Sunset aufgerufen werden soll sub SunSetShuttersAfterTimerFn { my $funcHash = shift; my $hash = $funcHash->{hash}; my $shuttersDev = $funcHash->{shuttersdevice}; $shutters->setShuttersDev($shuttersDev); my $homemode = $shutters->getRoommatesStatus; $homemode = $ascDev->getResidentsStatus if ( $homemode eq 'none' ); if ( $shutters->getDown ne 'roommate' && $ascDev->getAutoShuttersControlEvening eq 'on' && IsAfterShuttersManualBlocking($shuttersDev) && ( $shutters->getModeDown eq $homemode || ( $shutters->getModeDown eq 'absent' && $homemode eq 'gone' ) || $shutters->getModeDown eq 'always' ) && ( $ascDev->getSelfDefense eq 'off' || $shutters->getSelfDefenseMode eq 'off' || ( $ascDev->getSelfDefense eq 'on' && $ascDev->getResidentsStatus ne 'gone' ) ) && ( $shutters->getDown ne 'brightness' || ( $shutters->getDown eq 'brightness' && !$shutters->getSunset ) ) ) { if ( $shutters->getPrivacyDownStatus == 1 ) { $shutters->setPrivacyDownStatus(2); $shutters->setLastDrive('timer privacy night close'); ShuttersCommandSet( $hash, $shuttersDev, $shutters->getPrivacyDownPos ) if ( !$shutters->getQueryShuttersPos( $shutters->getPrivacyDownPos ) ); } else { $shutters->setPrivacyDownStatus(0) if ( $shutters->getPrivacyDownStatus == 2 ); $shutters->setLastDrive('night close'); ShuttersCommandSet( $hash, $shuttersDev, PositionValueWindowRec( $shuttersDev, ( $shutters->getSleepPos > 0 ? $shutters->getSleepPos : $shutters->getClosedPos ) ) ); } } if ( $shutters->getPrivacyDownStatus != 2 ) { $shutters->setSunrise(0); $shutters->setSunset(1); } CreateSunRiseSetShuttersTimer( $hash, $shuttersDev ); return; } ## Funktion welche beim Ablaufen des Timers für Sunrise aufgerufen werden soll sub SunRiseShuttersAfterTimerFn { my $funcHash = shift; my $hash = $funcHash->{hash}; my $shuttersDev = $funcHash->{shuttersdevice}; $shutters->setShuttersDev($shuttersDev); my $homemode = $shutters->getRoommatesStatus; $homemode = $ascDev->getResidentsStatus if ( $homemode eq 'none' ); if ( $shutters->getUp ne 'roommate' && $ascDev->getAutoShuttersControlMorning eq 'on' && ( $shutters->getModeUp eq $homemode || ( $shutters->getModeUp eq 'absent' && $homemode eq 'gone' ) || $shutters->getModeUp eq 'always' ) && ( $ascDev->getSelfDefense eq 'off' || $shutters->getSelfDefenseMode eq 'off' || ( $ascDev->getSelfDefense eq 'on' && ( ( $shutters->getSelfDefenseMode eq 'gone' && $ascDev->getResidentsStatus ne 'gone' ) || ( $shutters->getSelfDefenseMode eq 'absent' && $ascDev->getResidentsStatus ne 'absent' ) ) ) ) && ( $shutters->getUp ne 'brightness' || ( $shutters->getUp eq 'brightness' && !$shutters->getSunrise ) ) ) { if ( ( $shutters->getRoommatesStatus eq 'home' || $shutters->getRoommatesStatus eq 'awoken' || $shutters->getRoommatesStatus eq 'absent' || $shutters->getRoommatesStatus eq 'gone' || $shutters->getRoommatesStatus eq 'none' ) && ( $ascDev->getSelfDefense eq 'off' || ( $ascDev->getSelfDefense eq 'on' && CheckIfShuttersWindowRecOpen($shuttersDev) == 0 ) || ( $ascDev->getSelfDefense eq 'on' && CheckIfShuttersWindowRecOpen($shuttersDev) != 0 && ( $ascDev->getResidentsStatus ne 'absent' && $ascDev->getResidentsStatus ne 'gone' ) ) ) ) { if ( !$shutters->getIfInShading ) { if ( $shutters->getPrivacyUpStatus == 1 ) { $shutters->setPrivacyUpStatus(2); $shutters->setLastDrive('timer privacy day open'); ShuttersCommandSet( $hash, $shuttersDev, $shutters->getPrivacyUpPos ) if ( $shutters->getQueryShuttersPos( $shutters->getPrivacyUpPos ) ); } else { $shutters->setLastDrive('day open'); ShuttersCommandSet( $hash, $shuttersDev, $shutters->getOpenPos ); $shutters->setPrivacyUpStatus(0) if ( $shutters->getPrivacyUpStatus == 2 ); } } elsif ( $shutters->getIfInShading ) { $shutters->setLastDrive('shading in'); ShuttersCommandSet( $hash, $shuttersDev, $shutters->getShadingPos ); $shutters->setPrivacyUpStatus(0) if ( $shutters->getPrivacyUpStatus == 2 ); } } } if ( $shutters->getPrivacyUpStatus != 2 ) { $shutters->setSunrise(1); $shutters->setSunset(0); } CreateSunRiseSetShuttersTimer( $hash, $shuttersDev ); return; } sub CreateNewNotifyDev { my $hash = shift; my $name = $hash->{NAME}; $hash->{NOTIFYDEV} = "global," . $name; delete $hash->{monitoredDevs}; ::CommandDeleteReading( undef, $name . ' .monitoredDevs' ); my $shuttersList = ''; for my $shuttersDev ( @{ $hash->{helper}{shuttersList} } ) { AddNotifyDev( $hash, ::AttrVal( $shuttersDev, 'ASC_Roommate_Device', 'none' ), $shuttersDev, 'ASC_Roommate_Device' ) if ( ::AttrVal( $shuttersDev, 'ASC_Roommate_Device', 'none' ) ne 'none' ); AddNotifyDev( $hash, ::AttrVal( $shuttersDev, 'ASC_WindowRec', 'none' ), $shuttersDev, 'ASC_WindowRec' ) if ( ::AttrVal( $shuttersDev, 'ASC_WindowRec', 'none' ) ne 'none' ); AddNotifyDev( $hash, ::AttrVal( $shuttersDev, 'ASC_BrightnessSensor', 'none' ), $shuttersDev, 'ASC_BrightnessSensor' ) if ( ::AttrVal( $shuttersDev, 'ASC_BrightnessSensor', 'none' ) ne 'none' ); AddNotifyDev( $hash, ::AttrVal( $shuttersDev, 'ASC_ExternalTrigger', 'none' ), $shuttersDev, 'ASC_ExternalTrigger' ) if ( ::AttrVal( $shuttersDev, 'ASC_ExternalTrigger', 'none' ) ne 'none' ); $shuttersList = $shuttersList . ',' . $shuttersDev; } AddNotifyDev( $hash, ::AttrVal( $name, 'ASC_residentsDev', 'none' ), $name, 'ASC_residentsDev' ) if ( ::AttrVal( $name, 'ASC_residentsDev', 'none' ) ne 'none' ); AddNotifyDev( $hash, ::AttrVal( $name, 'ASC_rainSensor', 'none' ), $name, 'ASC_rainSensor' ) if ( ::AttrVal( $name, 'ASC_rainSensor', 'none' ) ne 'none' ); AddNotifyDev( $hash, ::AttrVal( $name, 'ASC_twilightDevice', 'none' ), $name, 'ASC_twilightDevice' ) if ( ::AttrVal( $name, 'ASC_twilightDevice', 'none' ) ne 'none' ); AddNotifyDev( $hash, ::AttrVal( $name, 'ASC_windSensor', 'none' ), $name, 'ASC_windSensor' ) if ( ::AttrVal( $name, 'ASC_windSensor', 'none' ) ne 'none' ); $hash->{NOTIFYDEV} = $hash->{NOTIFYDEV} . $shuttersList; return; } sub ShuttersInformation { my ( $FW_wname, $d, $room, $pageHash ) = @_; my $hash = $defs{$d}; return if ( !exists( $hash->{helper} ) || !defined( $hash->{helper}->{shuttersList} ) || ref( $hash->{helper}->{shuttersList} ) ne 'ARRAY' || scalar( @{ $hash->{helper}->{shuttersList} } ) == 0 || !defined( $shutters->getSunriseUnixTime ) || !defined( $shutters->getSunsetUnixTime ) ); my $ret = '

ASC Configuration and Information Summary

'; $ret .= '
'; $ret .= ''; $ret .= ''; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ''; my $linecount = 1; for my $shuttersDev ( @{ $hash->{helper}{shuttersList} } ) { $shutters->setShuttersDev($shuttersDev); if ( $linecount % 2 == 0 ) { $ret .= ''; } else { $ret .= ''; } $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ''; $linecount++; } $ret .= '
Shutters Next DriveUp Next DriveDown ASC Up ASC Down ASC Mode Up ASC Mode Down Partymode Lock-Out Last Drive Position Last Position Shading Info
 $shuttersDev   " . strftime( "%d.%m.%Y - %H:%M:%S", localtime( $shutters->getSunriseUnixTime ) ) . "   " . strftime( "%d.%m.%Y - %H:%M:%S", localtime( $shutters->getSunsetUnixTime ) ) . "   " . $shutters->getUp . "   " . $shutters->getDown . "   " . $shutters->getModeUp . "   " . $shutters->getModeDown . "   " . $shutters->getPartyMode . "   " . $shutters->getLockOut . "  " . ::ReadingsVal( $shuttersDev, 'ASC_ShuttersLastDrive', 'none' ) . "  " . $shutters->getStatus . "   " . $shutters->getLastPos . "   " . $shutters->getShadingStatus . ' - ' . strftime( "%H:%M:%S", localtime( $shutters->getShadingStatusTimestamp ) ) . " 


'; return $ret; } sub GetMonitoredDevs { my $hash = shift; my $notifydevs = eval { decode_json( ::ReadingsVal( $hash->{NAME}, '.monitoredDevs', 'none' ) ); }; my $ret = ''; $ret .= '
'; $ret .= ''; $ret .= ''; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ''; if ( ref($notifydevs) eq "HASH" ) { my $linecount = 1; for my $notifydev ( sort keys( %{$notifydevs} ) ) { if ( ref( $notifydevs->{$notifydev} ) eq "HASH" ) { for my $shuttersDev ( sort keys( %{ $notifydevs->{$notifydev} } ) ) { if ( $linecount % 2 == 0 ) { $ret .= ''; } else { $ret .= ''; } $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ""; $ret .= ''; $linecount++; } } } } $ret .= '
Shutters/ASC-Device NOTIFYDEV Attribut
$shuttersDev $notifydev $notifydevs->{$notifydev}{$shuttersDev}
'; return $ret; } sub _DetermineSlatCmd { my $value = shift; my $posValue = shift; return $posValue == $shutters->getShadingPos && $shutters->getShadingPositionAssignment ne 'none' ? $shutters->getShadingPositionAssignment : $posValue == $shutters->getVentilatePos && $shutters->getVentilatePositionAssignment ne 'none' ? $shutters->getVentilatePositionAssignment : $posValue == $shutters->getOpenPos && $shutters->getOpenPositionAssignment ne 'none' ? $shutters->getOpenPositionAssignment : $posValue == $shutters->getClosedPos && $shutters->getClosedPositionAssignment ne 'none' ? $shutters->getClosedPositionAssignment : $posValue == $shutters->getSleepPos && $shutters->getSleepPositionAssignment ne 'none' ? $shutters->getSleepPositionAssignment : $posValue == $shutters->getComfortOpenPos && $shutters->getComfortOpenPositionAssignment ne 'none' ? $shutters->getComfortOpenPositionAssignment : $posValue == $shutters->getPrivacyUpPos && $shutters->getPrivacyUpPositionAssignment ne 'none' ? $shutters->getPrivacyUpPositionAssignment : $posValue == $shutters->getPrivacyDownPos && $shutters->getPrivacyDownPositionAssignment ne 'none' ? $shutters->getPrivacyDownPositionAssignment : $value; } sub SetCmdFn { my $h = shift; my $shuttersDev = $h->{shuttersDev}; my $posValue = $h->{posValue}; $shutters->setShuttersDev($shuttersDev); $shutters->setLastDrive( $h->{lastDrive} ) if ( defined( $h->{lastDrive} ) ); my $idleDetectionValue = $shutters->getIdleDetectionValue; my $idleDetection = $shutters->getIdleDetection; return if ( $shutters->getASCenable eq 'off' || $ascDev->getASCenable eq 'off' || $idleDetection !~ m{^$idleDetectionValue$}xms || $FHEM::Automation::ShuttersControl::shutters ->getWindProtectionStatus eq 'protected' || $FHEM::Automation::ShuttersControl::shutters ->getRainProtectionStatus eq 'protected' ); if ( $shutters->getStatus != $posValue ) { $shutters->setLastPos( $shutters->getStatus ); } else { $shutters->setLastDrive( ::ReadingsVal( $shuttersDev, 'ASC_ShuttersLastDrive', 'none' ) ); ASC_Debug( 'FnSetCmdFn: ' . $shuttersDev . ' - Abbruch aktuelle Position ist gleich der Zielposition ' . $shutters->getStatus . '=' . $posValue ); return; } ASC_Debug( 'FnSetCmdFn: ' . $shuttersDev . ' - Rollo wird gefahren, aktuelle Position: ' . $shutters->getStatus . ', Zielposition: ' . $posValue . '. Grund der Fahrt: ' . $shutters->getLastDrive ); my $driveCommand = $shutters->getPosSetCmd . ' ' . $posValue; my $commandTemplate = $shutters->getCommandTemplate; my $slatPos = -1; if ( $shutters->getShadingPositionAssignment ne 'none' || $shutters->getOpenPositionAssignment ne 'none' || $shutters->getClosedPositionAssignment ne 'none' || $shutters->getPrivacyUpPositionAssignment ne 'none' || $shutters->getPrivacyDownPositionAssignment ne 'none' || $shutters->getSleepPositionAssignment ne 'none' || $shutters->getVentilatePositionAssignment ne 'none' || $shutters->getComfortOpenPositionAssignment ne 'none' ) { if ( ( $shutters->getShadingPositionAssignment =~ m{\A[a-zA-Z]+\z}xms && $shutters->getShadingPositionAssignment ne 'none' ) || ( $shutters->getOpenPositionAssignment =~ m{\A[a-zA-Z]+\z}xms && $shutters->getOpenPositionAssignment ne 'none' ) || ( $shutters->getClosedPositionAssignment =~ m{\A[a-zA-Z]+\z}xms && $shutters->getClosedPositionAssignment ne 'none' ) || ( $shutters->getPrivacyUpPositionAssignment =~ m{\A[a-zA-Z]+\z}xms && $shutters->getPrivacyUpPositionAssignment ne 'none' ) || ( $shutters->getPrivacyDownPositionAssignment =~ m{\A[a-zA-Z]+\z}xms && $shutters->getPrivacyDownPositionAssignment ne 'none' ) || ( $shutters->getSleepPositionAssignment =~ m{\A[a-zA-Z]+\z}xms && $shutters->getSleepPositionAssignment ne 'none' ) || ( $shutters->getVentilatePositionAssignment =~ m{\A[a-zA-Z]+\z}xms && $shutters->getVentilatePositionAssignment ne 'none' ) || ( $shutters->getComfortOpenPositionAssignment =~ m{\A[a-zA-Z]+\z}xms && $shutters->getComfortOpenPositionAssignment ne 'none' ) ) { $driveCommand = _DetermineSlatCmd( $driveCommand, $posValue ); } elsif ($shutters->getShadingPositionAssignment =~ m{\A\d{1,3}\z}xms || $shutters->getOpenPositionAssignment =~ m{\A\d{1,3}\z}xms || $shutters->getClosedPositionAssignment =~ m{\A\d{1,3}\z}xms || $shutters->getPrivacyUpPositionAssignment =~ m{\A\d{1,3}\z}xms || $shutters->getPrivacyDownPositionAssignment =~ m{\A\d{1,3}\z}xms || $shutters->getSleepPositionAssignment =~ m{\A\d{1,3}\z}xms || $shutters->getVentilatePositionAssignment =~ m{\A\d{1,3}\z}xms || $shutters->getComfortOpenPositionAssignment =~ m{\A\d{1,3}\z}xms ) { $slatPos = _DetermineSlatCmd( $slatPos, $posValue ); } } if ( $commandTemplate ne 'none' ) { # Patch von Beta-User Forum https://forum.fhem.de/index.php/topic,123659.0.html # Nutzervariablen setzen my %specials = ( '$name' => $shuttersDev, '$pos' => $posValue, '$slatPos' => $slatPos, '$cause' => $shutters->getLastDrive ); $commandTemplate = ::EvalSpecials( $commandTemplate, %specials ); # CMD ausführen ::AnalyzeCommandChain( $h, $commandTemplate ); } else { ::CommandSet( undef, $shuttersDev . ':FILTER=' . $shutters->getPosCmd . '!=' . $posValue . ' ' . $driveCommand ); ::InternalTimer( ::gettimeofday() + 3, sub() { ::CommandSet( undef, ( $shutters->getSlatDevice ne 'none' ? $shutters->getSlatDevice : $shuttersDev ) . ' ' . $shutters->getSlatPosCmd . ' ' . $slatPos ); }, $shuttersDev ) if ( $slatPos > -1 && $shutters->getSlatPosCmd ne 'none' ); } $shutters->setSelfDefenseAbsent( 0, 0 ) if (!$shutters->getSelfDefenseAbsent && $shutters->getSelfDefenseAbsentTimerrun ); return; } sub setShuttersLastDriveDelayed { my $h = shift; my $shuttersDevHash = $h->{devHash}; my $lastDrive = $h->{lastDrive}; ::readingsSingleUpdate( $shuttersDevHash, 'ASC_ShuttersLastDrive', $lastDrive, 1 ); return; } sub ASC_Debug { return if ( !::AttrVal( $ascDev->getName, 'ASC_debug', 0 ) ); my $debugMsg = shift; my $debugTimestamp = strftime( "%Y.%m.%d %T", localtime(time) ); print( encode_utf8( "\n" . 'ASC_DEBUG!!! ' . $debugTimestamp . ' - ' . $debugMsg . "\n" ) ); return; } sub PrivacyUpTime { my $shuttersDevHash = shift; my $shuttersSunriseUnixtime = shift; my $privacyUpUnixtime; if ( ( $shuttersSunriseUnixtime - $shutters->getPrivacyUpTime ) > ( ::gettimeofday() + 1 ) || $shutters->getPrivacyUpStatus == 2 ) { $privacyUpUnixtime = $shuttersSunriseUnixtime - $shutters->getPrivacyUpTime; $privacyUpUnixtime += 86400 if ( $shutters->getPrivacyUpStatus == 2 ); ::readingsSingleUpdate( $shuttersDevHash, 'ASC_Time_PrivacyDriveUp', strftime( "%d.%m.%Y - %H:%M", localtime($privacyUpUnixtime) ), 1 ); ## Setzt den PrivacyUp Modus für die Sichtschutzfahrt auf den Status 1 ## und gibt die Unixtime für die nächste Fahrt korrekt zurück if ( $shutters->getPrivacyUpStatus != 2 ) { $shutters->setPrivacyUpStatus(1); $shuttersSunriseUnixtime = $privacyUpUnixtime; } } else { ::readingsSingleUpdate( $shuttersDevHash, 'ASC_Time_PrivacyDriveUp', strftime( "%d.%m.%Y - %H:%M", localtime( ( $shuttersSunriseUnixtime - $shutters->getPrivacyUpTime ) + 86400 ) ), 1 ); } return $shuttersSunriseUnixtime; } sub PrivacyDownTime { my $shuttersDevHash = shift; my $shuttersSunsetUnixtime = shift; my $privacyDownUnixtime; if ( ( $shuttersSunsetUnixtime - $shutters->getPrivacyDownTime ) > ( ::gettimeofday() + 1 ) || $shutters->getPrivacyDownStatus == 2 ) { $privacyDownUnixtime = $shuttersSunsetUnixtime - $shutters->getPrivacyDownTime; $privacyDownUnixtime += 86400 if ( $shutters->getPrivacyDownStatus == 2 ); ::readingsSingleUpdate( $shuttersDevHash, 'ASC_Time_PrivacyDriveDown', strftime( "%d.%m.%Y - %H:%M", localtime($privacyDownUnixtime) ), 1 ); ## Setzt den PrivacyDown Modus für die Sichtschutzfahrt auf den Status 1 ## und gibt die Unixtime für die nächste Fahrt korrekt zurück if ( $shutters->getPrivacyDownStatus != 2 ) { $shutters->setPrivacyDownStatus(1); $shuttersSunsetUnixtime = $privacyDownUnixtime; } } else { ::readingsSingleUpdate( $shuttersDevHash, 'ASC_Time_PrivacyDriveDown', strftime( "%d.%m.%Y - %H:%M", localtime( ( $shuttersSunsetUnixtime - $shutters->getPrivacyDownTime ) + 86400 ) ), 1 ); } return $shuttersSunsetUnixtime; } sub DevStateIcon { my $hash = shift; $hash = $defs{$hash} if ( ref($hash) ne 'HASH' ); return if ( !$hash ); my $name = $hash->{NAME}; if ( ::ReadingsVal( $name, 'state', undef ) eq 'created new drive timer' ) { return '.*:clock'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'selfDefense terrace' ) { return '.*:fts_door_tilt'; } elsif ( ::ReadingsVal( $name, 'state', undef ) =~ m{.*asleep$}xms ) { return '.*:scene_sleeping'; } elsif ( ::ReadingsVal( $name, 'state', undef ) =~ m{^roommate(.come)?.(awoken|home)$}xms ) { return '.*:user_available'; } elsif ( ::ReadingsVal( $name, 'state', undef ) =~ m{^residents.(home|awoken)$}xms ) { return '.*:status_available'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'manual' ) { return '.*:fts_shutter_manual'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'selfDefense inactive' ) { return '.*:status_open'; } elsif ( ::ReadingsVal( $name, 'state', undef ) =~ m{^selfDefense.*.active$}xms ) { return '.*:status_locked'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'day open' ) { return '.*:scene_day'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'night close' ) { return '.*:scene_night'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'shading in' ) { return '.*:fts_shutter_shadding_run'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'shading out' ) { return '.*:fts_shutter_shadding_stop'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'active' ) { return '.*:hourglass'; } elsif ( ::ReadingsVal( $name, 'state', undef ) =~ m{.*privacy.*}xms ) { return '.*:fts_shutter_50'; } elsif ( ::ReadingsVal( $name, 'state', undef ) eq 'adv delay close' ) { return '.*:christmas_tree'; } return; } sub RemoveShuttersTimer { my $hash = shift; for my $shuttersDev ( @{ $hash->{helper}{shuttersList} } ) { $shutters->setShuttersDev($shuttersDev); ::RemoveInternalTimer( $shutters->getInTimerFuncHash ); $shutters->setInTimerFuncHash(undef); } return; } 1; __END__