diff --git a/FHEM/44_ROLLO.pm b/FHEM/44_ROLLO.pm new file mode 100644 index 000000000..69d4e08d1 --- /dev/null +++ b/FHEM/44_ROLLO.pm @@ -0,0 +1,1103 @@ +######################################################################################## +# $Id$ # +# Modul zur einfacheren Rolladensteuerung # +# # +# Thomas Ramm, 2016 # +# Tim Horenkamp, 2018 # +# Markus Moises, 2016 # +# Mirko Lindner, 2018 # +# KernSani, 2017 # +# # +######################################################################################## +# +# This programm 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. +# +# 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. +# +######################################################################################## +package main; + +use strict; +use warnings; + +my $version = "1.401"; + +my %sets = ( + "open" => "noArg", + "closed" => "noArg", + "up" => "noArg", + "down" => "noArg", + "half" => "noArg", + "stop" => "noArg", + "blocked" => "noArg", + "unblocked" => "noArg", + "pct" => "0,10,20,30,40,50,60,70,80,90,100", + "reset" => "open,closed", + "extern" => "open,closed,stop", + "drive" => "textField" +); + +my %pcts = ( + "open" => 0, + "closed" => 100, + "half" => 50 +); + +my %gets = ( "version:noArg" => "V" ); + +############################################################ INITIALIZE ##### +sub ROLLO_Initialize($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + $hash->{DefFn} = "ROLLO_Define"; + $hash->{UndefFn} = "ROLLO_Undef"; + $hash->{SetFn} = "ROLLO_Set"; + $hash->{GetFn} = "ROLLO_Get"; + $hash->{AttrFn} = "ROLLO_Attr"; + + $hash->{AttrList} = + " rl_secondsDown" + . " rl_secondsUp" + . " rl_excessTop" + . " rl_excessBottom" + . " rl_switchTime" + . " rl_resetTime" + . " rl_reactionTime" + . " rl_blockMode:blocked,force-open,force-closed,only-up,only-down,half-up,half-down,none" + . " rl_commandUp rl_commandUp2 rl_commandUp3" + . " rl_commandDown rl_commandDown2 rl_commandDown3" + . " rl_commandStop rl_commandStopDown rl_commandStopUp" + . " automatic-enabled:on,off" + . " automatic-delay" + . " rl_autoStop:1,0" + . " rl_type:normal,HomeKit" + . " disable:0,1" + . " rl_forceDrive:0,1" + . " rl_noSetPosBlocked:0,1" . " " + . $readingFnAttributes; + + $hash->{stoptime} = 0; + + #map new Attribute names + $hash->{AttrRenameMap} = { + "secondsDown" => "rl_secondsDown", + "secondsUp" => "rl_secondsUp", + "excessTop" => "rl_excessTop", + "excessBottom" => "rl_excessBottom", + "switchTime" => "rl_switchTime", + "resetTime" => "rl_resetTime", + "reactionTime" => "rl_resetTime", + "blockMode" => "rl_blockMode", + "commandUp" => "rl_commandUp", + "commandUp2" => "rl_commandUp2", + "commandUp3" => "rl_commandUp3", + "commandDown" => "rl_commandDown", + "commandDown2" => "rl_commandDown2", + "commandDown3" => "rl_commandDown3", + "commandStop" => "rl_commandStop", + "commandStopUp" => "rl_commandStopUp", + "commandStopDown" => "rl_commandStopDown", + "autoStop" => "rl_autoStop", + "type" => "rl_type", + "forceDrive" => "rl_forceDrive", + "noSetPosBlocked" => "rl_noSetPosBlocked" + }; + + return undef; +} + +################################################################ DEFINE ##### +sub ROLLO_Define($$) { + my ( $hash, $def ) = @_; + my $name = $hash->{NAME}; + Log3 $name, 5, "ROLLO ($name) >> Define"; + + my @a = split( "[ \t][ \t]*", $def ); + + # no direct access to %attr - KernSani 13.01.2019 + CommandAttr( undef, $name . " rl_secondsDown 30" ) + if ( AttrVal( $name, "rl_secondsDown", "" ) eq "" ); + + #$attr{$name}{"rl_secondsDown"} = 30; + CommandAttr( undef, $name . " rl_secondsUp 30" ) + if ( AttrVal( $name, "rl_secondsUp", "" ) eq "" ); + + #$attr{$name}{"rl_secondsUp"} = 30; + CommandAttr( undef, $name . " rl_excessTop 4" ) + if ( AttrVal( $name, "rl_excessTop", "" ) eq "" ); + + #$attr{$name}{"rl_excessTop"} = 4; + CommandAttr( undef, $name . " rl_excessBottom 2" ) + if ( AttrVal( $name, "rl_excessBottom", "" ) eq "" ); + + #$attr{$name}{"rl_excessBottom"} = 2; + CommandAttr( undef, $name . " rl_switchTime 1" ) + if ( AttrVal( $name, "rl_switchTime", "" ) eq "" ); + + #$attr{$name}{"rl_switchTime"} = 1; + CommandAttr( undef, $name . " rl_resetTime 0" ) + if ( AttrVal( $name, "rl_switchTime", "" ) eq "" ); + + #$attr{$name}{"rl_resetTime"} = 0; + CommandAttr( undef, $name . " rl_autoStop 0" ) + if ( AttrVal( $name, "rl_autoStop", "" ) eq "" ); + + #fix devstateicon - KernSani 13.01.2019 + my $devStateIcon = +'open:fts_shutter_10:closed closed:fts_shutter_100:open half:fts_shutter_50:closed drive-up:fts_shutter_up@red:stop drive-down:fts_shutter_down@red:stop pct-100:fts_shutter_100:open pct-90:fts_shutter_80:closed pct-80:fts_shutter_80:closed pct-70:fts_shutter_70:closed pct-60:fts_shutter_60:closed pct-50:fts_shutter_50:closed pct-40:fts_shutter_40:open pct-30:fts_shutter_30:open pct-20:fts_shutter_20:open pct-10:fts_shutter_10:open pct-0:fts_shutter_10:closed'; + CommandAttr( undef, $name . " devStateIcon $devStateIcon" ) + if ( AttrVal( $name, "devStateIcon", "" ) eq "" ); + CommandAttr( undef, $name . " rl_type normal" ) + if ( AttrVal( $name, "rl_type", "" ) eq "" ); + +#$attr{$name}{"rl_type"} = "normal"; #neue Attribute sollten als default keine Änderung an der Funktionsweise bewirken. + CommandAttr( undef, $name . " webCmd open:closed:half:stop:pct" ) + if ( AttrVal( $name, "webCmd", "" ) eq "" ); + + #cmdIcon aded - KernSani 13.01.2019 + CommandAttr( undef, + $name . " cmdIcon open:fts_shutter_up closed:fts_shutter_down stop:fts_shutter_manual half:fts_shutter_50" ) + if ( AttrVal( $name, "cmdIcon", "" ) eq "" ); + + #$attr{$name}{"webCmd"} = "open:closed:half:stop:pct"; + + # $attr{$name}{"blockMode"} = "none"; + + if ( IsDisabled($name) ) { + readingsSingleUpdate( $hash, "state", "inactive", 1 ); + $hash->{helper}{DISABLED} = 1; + } + + return undef; +} + +################################################################# UNDEF ##### +sub ROLLO_Undef($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + RemoveInternalTimer($hash); + return undef; +} + +#################################################################### SET ##### +sub ROLLO_Set($@) { + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + return undef if IsDisabled($name); + + #Warum steht das hier? Verschoben in Define - KernSani 13.01.2019 + #$attr{$name}{"webCmd"} = "open:closed:half:stop:pct"; + if ( ReadingsVal( $name, "position", "exists" ) ne "exists" ) { + +#Log3 $name,1, "ROLLO ($name) Readings position and desired_position aren't used anymore. Execute \"deletereading $name position\" and \"deletereading $name desired_position\" to remove them"; + } + + #allgemeine Fehler in der Parameterübergabe abfangen + if ( @a < 2 ) { + Log3 $name, 2, "ERROR: \"set ROLLO\" needs at least one argument"; + return "\"ROLLO_Set\" needs at least one argument"; + } + my $cmd = $a[1]; + + # Keep command "position" for a while + if ( $cmd eq "position" ) { + $cmd = "pct"; + Log3 $name, 1, + "ROLLO ($name) Set command \"position\" is deprecated. Please change your definitions to \"pct\""; + } + + my $arg = ""; + $arg = $a[2] if defined $a[2]; + my $arg2 = ""; + $arg2 = $a[3] if defined $a[3]; + + Log3 $name, 5, "ROLLO ($name) >> Set ($cmd,$arg)" if ( $cmd ne "?" ); + + my @pctsets = ( "0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" ); + + if ( !defined( $sets{$cmd} ) && $cmd !~ @pctsets ) { + my $param = ""; + foreach my $val ( keys %sets ) { + $param .= " $val:$sets{$val}"; + } + + Log3 $name, 2, "ERROR: Unknown command $cmd, choose one of $param" if ( $cmd ne "?" ); + return "Unknown argument $cmd, choose one of $param"; + } + + #### Stop if not driving - do we need that? + if ( ( $cmd eq "stop" ) && ( ReadingsVal( $name, "state", '' ) !~ /drive/ ) ) { + Log3 $name, 3, "WARNING: command is stop but shutter is not driving!"; + RemoveInternalTimer($hash); + ROLLO_Stop($hash); + return undef; + } + + ##### Commands without IO + if ( $cmd eq "extern" ) { + readingsSingleUpdate( $hash, "drive-type", "extern", 1 ); + $cmd = $arg; + } + elsif ( $cmd eq "reset" ) { + my $reset_pct = $pcts{$arg}; + $reset_pct = 100 - $reset_pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" ); + + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "state", $arg ); + readingsBulkUpdate( $hash, "desired_pct", $reset_pct ); + readingsBulkUpdate( $hash, "pct", $reset_pct ); + readingsEndUpdate( $hash, 1 ); + return undef; + } + + ##### Block commands + if ( $cmd eq "blocked" ) { + ROLLO_Stop($hash); + readingsSingleUpdate( $hash, "blocked", "1", 1 ); + return if ( AttrVal( $name, "rl_blockMode", "none" ) eq "blocked" ); + } + elsif ( $cmd eq "unblocked" ) { + + # Wenn blocked=1 wird in Rollo_Stop der state auf "blocked" gesetzt + # daher erst blocked auf 0 (Stop ist m.E. an dieser Stelle eigentlich nicht notwendig) + #ROLLO_Stop($hash); #delete KernSani + readingsSingleUpdate( $hash, "blocked", "0", 1 ); + ROLLO_Stop($hash); #add KernSani + ROLLO_Start($hash); + + #avoid the deletereading mesage in Log - KernSani 30.12.2018 + #fhem("deletereading $name blocked"); + #readingsDelete( $hash, "blocked" ); + CommandDeleteReading( undef, "$name blocked" ); + return; + } + + ##### Drive for Seconds - Basic Implementation (doesn't consider block mode, Homekit, ...) + if ( $cmd eq "drive" ) { + return "Drive needs two arguments, the direction and the time in seconds" if ( !$arg2 ); + my $direction = $arg; + $arg = undef; + my $time = $arg2; + $hash->{driveTime} = $time; + $hash->{driveDir} = $direction; + my $dpct = ROLLO_calculateDesiredPosition( $hash, $name ); + readingsSingleUpdate( $hash, "desired_pct", $dpct, 1 ); + Log3 $name, 3, "ROLLO ($name) DRIVE Command drive $direction for $time seconds. "; + ROLLO_Stop($hash); + ROLLO_Drive( $hash, $time, $direction, $cmd ); + return undef; + } + + ##### now do the real drive stuff + + my $desiredPos = $cmd; + Log3 $name, 5, "ROLLO ($name) DesiredPos set to $desiredPos, ($arg) "; + my $typ = AttrVal( $name, "rl_type", "normal" ); + + # Allow all positions + #if ( grep /^$arg$/, @pctsets ) + #Log3 $name, 5, "ROLLO ($name) Arg is $arg"; + #change sequence to avoid "is not numeric" warning + #if ($arg && $arg =~ /^[0-9,.E]*$/ && $arg >= 0 && $arg <= 100 ) + if ( $arg ne "" && $arg >= 0 && $arg <= 100 ) { + if ( $cmd eq "pct" ) { + if ( $typ eq "HomeKit" ) { + Log3 $name, 4, "ROLLO ($name) invert pct from $arg to (100-$arg)"; + $arg = 100 - $arg; + } + $cmd = "pct-" . $arg; + $desiredPos = $arg; + Log3 $name, 5, "ROLLO ($name) DesiredPos now $desiredPos, $arg"; + } + else { #I think this shouldn't happen... + if ( $typ eq "HomeKit" ) { + $cmd = 100 - $cmd; + } + $cmd = "pct-" . $cmd; + $desiredPos = $cmd; + Log3 $name, 5, "ROLLO ($name) There is an arg $arg, but command is $cmd"; + } + } + else { + if ( $cmd eq "down" || $cmd eq "up" ) { + + # Recalculate the desired pct + my $posin = ReadingsVal( $name, "pct", 0 ); + $posin = 100 - $posin if ( $typ eq "HomeKit" ); + $desiredPos = int( ( $posin - 10 ) / 10 + 0.5 ) * 10; + $desiredPos = int( ( $posin + 10 ) / 10 + 0.5 ) * 10 if $cmd eq "down"; + $desiredPos = 100 if $desiredPos > 100; + $desiredPos = 0 if $desiredPos < 0; + } + else { + $desiredPos = $pcts{$cmd}; + } + +# Ich verstehe nicht wann nachfolgender Zustand eintreten kann, das Coding führt aber dazu, dass pct 0 (open) auf "none" gesetzt wird +#$desiredPos = "none" if !$desiredPos || $desiredPos eq ""; + } + Log3 $name, 5, "ROLLO ($name) DesiredPos now $desiredPos, $cmd"; + + #wenn ich gerade am fahren bin und eine neue Zielposition angefahren werden soll, + # muss ich jetzt erst mal meine aktuelle Position berechnen und updaten + # bevor ich die desired-position überschreibe! + + if ( ( ReadingsVal( $name, "state", "" ) =~ /drive-/ ) ) { + my $pct = ROLLO_calculatepct( $hash, $name ); + readingsSingleUpdate( $hash, "pct", $pct, 1 ); + + # Desired-position sollte auf aktuelle position gesetzt werden, wenn explizit gestoppt wird. + readingsSingleUpdate( $hash, "desired_pct", $pct, 1 ) + if ( ( $cmd eq "stop" || $cmd eq "blocked" ) && $pct > 0 && $pct < 100 ); + } + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "command", $cmd ); + + # desired position sollte nicht gesetzt werden, wenn ein unerlaubter Befehl (wenn Rollladen geblockt ist) + # gesendet wird. Sonst rennt er direkt nach dem "unblock" los + # readingsBulkUpdate($hash,"desired_position",$desiredPos) if($cmd ne "blocked") && ($cmd ne "stop") + readingsBulkUpdate( $hash, "desired_pct", $desiredPos ) + if ( $cmd ne "blocked" ) && ( $cmd ne "stop" ) && ROLLO_isAllowed( $hash, $cmd, $desiredPos ); + readingsEndUpdate( $hash, 1 ); + + ROLLO_Start($hash); + return undef; +} +#################################################################### isAllowed ##### +sub ROLLO_isAllowed($$$) { + my ( $hash, $cmd, $desired_pct ) = @_; + my $name = $hash->{NAME}; + + if ( ReadingsVal( $name, "blocked", "0" ) ne "1" or AttrVal( $name, "rl_noSetPosBlocked", 0 ) == 0 ) { + return 1; + } + my $pct = ReadingsVal( $name, "pct", undef ); + $pct = 100 - $pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" ); # KernSani 30.12.2018 + my $blockmode = AttrVal( $name, "rl_blockMode", "none" ); + Log3 $name, 3, "ROLLO ($name) >> Blockmode:$blockmode $pct-->$desired_pct"; + if ( $blockmode eq "blocked" + || ( $blockmode eq "only-up" && $pct <= $desired_pct ) + || ( $blockmode eq "only-down" && $pct >= $desired_pct ) ) + { + return undef; + } + return 1; +} + +#**************************************************************************** +sub ROLLO_Drive { + my ( $hash, $time, $direction, $command ) = @_; + my $name = $hash->{NAME}; + my ( $command1, $command2, $command3 ); + if ( $direction eq "down" ) { + $command1 = AttrVal( $name, 'rl_commandDown', "" ); + $command2 = AttrVal( $name, 'rl_commandDown2', "" ); + $command3 = AttrVal( $name, 'rl_commandDown3', "" ); + } + else { + $command1 = AttrVal( $name, 'rl_commandUp', "" ); + $command2 = AttrVal( $name, 'rl_commandUp2', "" ); + $command3 = AttrVal( $name, 'rl_commandUp3', "" ); + } + + $command = "drive-" . $direction; + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "last_drive", $command ); + readingsBulkUpdate( $hash, "state", $command ); + readingsEndUpdate( $hash, 1 ); + + #***** ROLLO NICHT LOSFAHREN WENN SCHON EXTERN GESTARTET *****# + if ( ReadingsVal( $name, "drive-type", "undef" ) ne "extern" ) { + Log3 $name, 4, "ROLLO ($name) execute following commands: $command1; $command2; $command3"; + readingsSingleUpdate( $hash, "drive-type", "modul", 1 ); + + #no fhem() - KernSani 13.01.2019 + my $ret = AnalyzeCommandChain( undef, "$command1" ) if ( $command1 ne "" ); + Log3 $name, 1, "ROLLO ($name) $ret" if ( defined($ret) ); + AnalyzeCommandChain( undef, "$command2" ) if ( $command2 ne "" ); + AnalyzeCommandChain( undef, "$command3" ) if ( $command3 ne "" ); + } + else { + #readingsSingleUpdate($hash,"drive-type","extern",1); + readingsSingleUpdate( $hash, "drive-type", "na", 1 ); + Log3 $name, 4, "ROLLO ($name) drive-type is extern, not executing driving commands"; + } + + $hash->{stoptime} = int( gettimeofday() + $time ); + InternalTimer( $hash->{stoptime}, "ROLLO_Timer", $hash, 1 ); + Log3 $name, 4, "ROLLO ($name) stop in $time seconds."; +} + +#################################################################### START ##### +sub ROLLO_Start($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + Log3 $name, 5, "ROLLO ($name) >> Start"; + + my $command = ReadingsVal( $name, "command", "stop" ); + my $desired_pct = ReadingsVal( $name, "desired_pct", 100 ); + my $pct = ReadingsVal( $name, "pct", 0 ); + $pct = 100 - $pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" ); + my $state = ReadingsVal( $name, "state", "open" ); + + Log3 $name, 4, "ROLLO ($name) drive from $pct to $desired_pct. command: $command. state: $state"; + + if ( ReadingsVal( $name, "blocked", "0" ) eq "1" && $command ne "stop" ) { + my $blockmode = AttrVal( $name, "rl_blockMode", "none" ); + Log3 $name, 4, "ROLLO ($name) block mode: $blockmode - $pct to $desired_pct?"; + + if ( $blockmode eq "blocked" ) { + readingsSingleUpdate( $hash, "state", "blocked", 1 ); + return; + } + elsif ( $blockmode eq "force-open" ) { + $desired_pct = 0; + } + elsif ( $blockmode eq "force-closed" ) { + $desired_pct = 100; + } + elsif ( $blockmode eq "only-up" && $pct <= $desired_pct ) { + readingsSingleUpdate( $hash, "state", "blocked", 1 ); + return; + } + elsif ( $blockmode eq "only-down" && $pct >= $desired_pct ) { + readingsSingleUpdate( $hash, "state", "blocked", 1 ); + return; + } + elsif ( $blockmode eq "half-up" && $desired_pct < 50 ) { + $desired_pct = 50; + } + elsif ( $blockmode eq "half-up" && $desired_pct == 50 ) { + readingsSingleUpdate( $hash, "state", "blocked", 1 ); + return; + } + elsif ( $blockmode eq "half-down" && $desired_pct > 50 ) { + $desired_pct = 50; + } + elsif ( $blockmode eq "half-down" && $desired_pct == 50 ) { + readingsSingleUpdate( $hash, "state", "blocked", 1 ); + return; + } + + #desired_pct has to be updated - KernSani 30.12.2018 + readingsSingleUpdate( $hash, "desired_pct", $desired_pct, 1 ); + } + + my $direction = "down"; + $direction = "up" if ( $pct > $desired_pct || $desired_pct == 0 ); + + #if ( $hash->{driveDir} ) { $direction = $hash->{driveDir} } + Log3 $name, 4, "ROLLO ($name) pct: $pct -> $desired_pct / direction: $direction"; + + #Ich fahre ja gerade...wo bin ich aktuell? + if ( $state =~ /drive-/ ) { + + #$pct = ROLLO_calculatepct($hash,$name); #das muss weg.. verschoben in set! + + if ( $command eq "stop" ) { + ROLLO_Stop($hash); + return; + } + + #$direction = "down"; + #$direction = "up" if ($pct > $desired_pct || $desired_pct == 0); + if ( ( ( $state eq "drive-down" ) && ( $direction eq "up" ) ) + || ( ( $state eq "drive-up" ) && ( $direction eq "down" ) ) ) + { + Log3 $name, 3, "driving into wrong direction. stop and change driving direction"; + ROLLO_Stop($hash); + InternalTimer( int( gettimeofday() ) + AttrVal( $name, 'rl_switchTime', 0 ), "ROLLO_Start", $hash, 0 ); + return; + } + } + + my $time = 0; + + RemoveInternalTimer($hash); + + $time = ROLLO_calculateDriveTime( $name, $pct, $desired_pct, $direction ); + + if ( $time > 0 ) { + ROLLO_Drive( $hash, $time, $direction, $command ); + } + + return undef; +} + +#**************************************************************************** +sub ROLLO_Timer($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + Log3 $name, 5, "ROLLO ($name) >> Timer"; + + my $pct = ReadingsVal( $name, "desired_pct", 0 ); + $pct = 100 - $pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" ); + + readingsSingleUpdate( $hash, "pct", $pct, 1 ); + ROLLO_Stop($hash); + + return undef; +} + +#**************************************************************************** +sub ROLLO_Stop($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + #my $command = ReadingsVal($name,"command","stop"); + + Log3 $name, 5, "ROLLO ($name) >> Stop"; + + RemoveInternalTimer($hash); + my $pct = ReadingsVal( $name, "pct", 0 ); + $pct = 100 - $pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" ); + my $state = ReadingsVal( $name, "state", "" ); + + Log3 $name, 4, "ROLLO ($name) stops from $state at pct $pct"; + + #wenn autostop=1 und pct <> 0+100 und rollo fährt, dann kein stopbefehl ausführen... + if ( ( $state =~ /drive-/ && $pct >= 0 && $pct <= 100 ) || AttrVal( $name, "rl_autoStop", 0 ) ne 1 ) { + my $command = AttrVal( $name, 'rl_commandStop', "" ); + $command = AttrVal( $name, 'rl_commandStopUp', "" ) if ( AttrVal( $name, 'rl_commandStopUp', "" ) ne "" ); + $command = AttrVal( $name, 'rl_commandStopDown', "" ) + if ( AttrVal( $name, 'rl_commandStopDown', "" ) ne "" && $state eq "drive-down" ); + + # NUR WENN NICHT BEREITS EXTERN GESTOPPT + if ( ReadingsVal( $name, "drive-type", "undef" ) ne "extern" ) { + AnalyzeCommandChain( undef, "$command" ) if ( $command ne "" ); + Log3 $name, 4, "ROLLO ($name) stopped by excuting the command: $command"; + } + else { + readingsSingleUpdate( $hash, "drive-type", "na", 1 ); + Log3 $name, 4, "ROLLO ($name) is in drive-type extern"; + } + } + else { + Log3 $name, 4, "ROLLO ($name) drives to end pct and autostop is enabled. No stop command executed"; + } + + if ( ReadingsVal( $name, "blocked", "0" ) eq "1" && AttrVal( $name, "rl_blockMode", "none" ) ne "none" ) { + readingsSingleUpdate( $hash, "state", "blocked", 1 ); + } + else { + #Runden der pct auf volle 10%-Schritte für das Icon + my $newpos = int( $pct / 10 + 0.5 ) * 10; + $newpos = 0 if ( $newpos < 0 ); + $newpos = 100 if ( $newpos > 100 ); + + my $state; + + #pct in text umwandeln + my %rhash = reverse %pcts; + + if ( defined( $rhash{$newpos} ) ) { + + #ich kenne keinen Text für die pct, also als pct-nn anzeigen + $state = $rhash{$newpos}; + } + else { + $newpos = 100 - $newpos if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" ); + $state = "pct-$newpos"; + } + + readingsSingleUpdate( $hash, "state", $state, 1 ); + } + + return undef; +} + +#**************************************************************************** +sub ROLLO_calculatepct(@) { + my ( $hash, $name ) = @_; + my ($pct); + Log3 $name, 5, "ROLLO ($name) >> calculatepct"; + + my $type = AttrVal( $name, "rl_type", "normal" ); + my $start = ReadingsVal( $name, "pct", 100 ); + $start = 100 - $start if $type eq "HomeKit"; + + my $end = ReadingsVal( $name, "desired_pct", 0 ); + my $drivetime_rest = int( $hash->{stoptime} - gettimeofday() ); #die noch zu fahrenden Sekunden + my $drivetime_total = + ( $start < $end ) ? AttrVal( $name, 'rl_secondsDown', undef ) : AttrVal( $name, 'rl_secondsUp', undef ); + + # bsp: die fahrzeit von 0->100 ist 26sec. ich habe noch 6sec. zu fahren...was bedeutet das? + # excessTop = 5sec + # driveTimeDown=20sec -> hier wird von 0->100 gezählt, also pro sekunde 5 Schritte + # excessBottom = 1sec + # aktuelle pct = 6sec-1sec=5sec pctsfahrzeit=25steps=pct75 + + #Frage1: habe ich noch "tote" Sekunden vor mir wegen endpct? + my $resetTime = AttrVal( $name, 'rl_resetTime', 0 ); + $drivetime_rest -= ( AttrVal( $name, 'rl_excessTop', 0 ) + $resetTime ) if ( $end == 0 ); + $drivetime_rest -= ( AttrVal( $name, 'rl_excessBottom', 0 ) + $resetTime ) if ( $end == 100 ); + + #wenn ich schon in der nachlaufzeit war, setze ich die pct auf 99, dann kann man nochmal für die nachlaufzeit starten + if ( $start == $end ) { + $pct = $end; + } + elsif ( $drivetime_rest < 0 ) { + $pct = ( $start < $end ) ? 99 : 1; + } + else { + $pct = $drivetime_rest / $drivetime_total * 100; + $pct = ( $start < $end ) ? $end - $pct : $end + $pct; + $pct = 0 if ( $pct < 0 ); + $pct = 100 if ( $pct > 100 ); + } + + #aktuelle pct aktualisieren und zurückgeben + Log3 $name, 4, "ROLLO ($name) calculated pct is $pct; rest drivetime is $drivetime_rest"; + my $savepos = $pct; + $savepos = 100 - $pct if $type eq "HomeKit"; + readingsSingleUpdate( $hash, "pct", $savepos, 100 ); + + return $pct; +} + +#**************************************************************************** +sub ROLLO_calculateDesiredPosition(@) { + my ( $hash, $name ) = @_; + my ($pct); + Log3 $name, 5, "ROLLO ($name) >> calculateDesiredPosition"; + + my $start = 0; + my $dtime = $hash->{driveTime}; + my $direction = $hash->{driveDir}; + my $typ = AttrVal( $name, "rl_type", "normal" ); + Log3 $name, 4, "ROLLO ($name) drive $direction for $dtime"; + my ( $time, $steps ); + if ( $direction eq "up" ) { + $time = AttrVal( $name, 'rl_secondsUp', undef ); + $start = 100; + } + else { + $time = AttrVal( $name, 'rl_secondsDown', undef ); + $start = 0; + } + + my $startPos = ReadingsVal( $name, "pct", 100 ); + $startPos = 100 - $startPos if ( $typ eq "HomeKit" ); + + $time += AttrVal( $name, 'rl_reactionTime', 0 ); + + #$time += AttrVal($name,'excessTop',0) if($startPos == 0); + #$time += AttrVal($name,'excessBottom',0) if($startPos == 100); + #$time += AttrVal($name,'resetTime', 0) if($startPos == 0 or $startPos == 100); + + $steps = $dtime / $time * 100; + Log3 $name, 4, "ROLLO ($name) total time = $time, we're intending to drive $steps steps"; + if ( $direction eq "up" ) { + $pct = $startPos - $steps; + } + else { + $pct = $startPos + $steps; + } + $pct = 100 if ( $pct > 100 ); + $pct = 0 if ( $pct < 0 ); + + Log3 $name, 4, "ROLLO ($name) Target pct is $pct"; + return int($pct); +} + +#**************************************************************************** +sub ROLLO_calculateDriveTime(@) { + my ( $name, $oldpos, $newpos, $direction ) = @_; + Log3 $name, 5, "ROLLO ($name) >> calculateDriveTime | going $direction: from $oldpos to $newpos"; + + my ( $time, $steps ); + if ( $direction eq "up" ) { + $time = AttrVal( $name, 'rl_secondsUp', undef ); + $steps = $oldpos - $newpos; + } + else { + $time = AttrVal( $name, 'rl_secondsDown', undef ); + $steps = $newpos - $oldpos; + } + if ( $steps == 0 ) { + Log3 $name, 4, "ROLLO ($name) already at position!"; + + # Wenn force-Drive gesetzt ist fahren wir immer 100% (wenn "open" oder "closed") + if ( AttrVal( $name, "rl_forceDrive", 0 ) == 1 && ( $oldpos == 0 || $oldpos == 100 ) ) { + Log3 $name, 4, "ROLLO ($name): forceDrive set, driving $direction"; + $steps = 100; + } + } + + if ( !defined($time) ) { + Log3 $name, 2, "ROLLO ($name) ERROR: missing attribute secondsUp or secondsDown"; + $time = 60; + } + + my $drivetime = $time * $steps / 100; + + # reactionTime etc... sollten nur hinzugefügt werden, wenn auch gefahren wird... + if ( $drivetime > 0 ) { + $drivetime += AttrVal( $name, 'rl_reactionTime', 0 ) if ( $time > 0 && $steps > 0 ); + + $drivetime += AttrVal( $name, 'rl_excessTop', 0 ) if ( $oldpos == 0 or $newpos == 0 ); + $drivetime += AttrVal( $name, 'rl_excessBottom', 0 ) if ( $oldpos == 100 or $newpos == 100 ); + $drivetime += AttrVal( $name, 'rl_resetTime', 0 ) if ( $newpos == 0 or $newpos == 100 ); + Log3 $name, 4, +"ROLLO ($name) calculateDriveTime: oldpos=$oldpos,newpos=$newpos,direction=$direction,time=$time,steps=$steps,drivetime=$drivetime"; + + } + return $drivetime; +} + +################################################################### GET ##### +sub ROLLO_Get($@) { + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + Log3 $name, 5, "ROLLO ($name) >> Get"; + + #-- get version + if ( $a[1] eq "version" ) { + return "$name.version => $version"; + } + if ( @a < 2 ) { + Log3 $name, 2, "ROLLO ($name) ERROR: \"get ROLLO\" needs at least one argument"; + return "\"get ROLLO\" needs at least one argument"; + } + + my $cmd = $a[1]; + if ( !$gets{$cmd} ) { + my @cList = keys %gets; + Log3 $name, 3, "ERROR: Unknown argument $cmd, choose one of " . join( " ", @cList ) if ( $cmd ne "?" ); + return "Unknown argument $cmd, choose one of " . join( " ", @cList ); + } + + my $val = ""; + $val = $a[2] if ( @a > 2 ); + Log3 $name, 4, "ROLLO ($name) command: $cmd, value: $val"; +} + +################################################################## ATTR ##### +sub ROLLO_Attr(@) { + my ( $cmd, $name, $aName, $aVal ) = @_; + Log3 $name, 5, "ROLLO ($name) >> Attr"; + my $hash = $defs{$name}; + + if ( $cmd eq "set" ) { + if ( $aName eq "Regex" ) { + eval { qr/$aVal/ }; + if ($@) { + Log3 $name, 2, "ROLLO ($name):ERROR Invalid regex in attr $name $aName $aVal: $@"; + return "Invalid Regex $aVal"; + } + } + + #Auswertung von HomeKit und dem Logo + if ( $aName eq "rl_type" ) { + + #auslesen des aktuellen Icon, wenn es nicht gesetzt ist, oder dem default entspricht, dann neue Zuweisung vornehmen + my $iconNormal = +'open:fts_shutter_10:closed closed:fts_shutter_100:open half:fts_shutter_50:closed drive-up:fts_shutter_up@red:stop drive-down:fts_shutter_down@red:stop pct-100:fts_shutter_100:open pct-90:fts_shutter_80:closed pct-80:fts_shutter_80:closed pct-70:fts_shutter_70:closed pct-60:fts_shutter_60:closed pct-50:fts_shutter_50:closed pct-40:fts_shutter_40:open pct-30:fts_shutter_30:open pct-20:fts_shutter_20:open pct-10:fts_shutter_10:open pct-0:fts_shutter_10:closed'; + my $iconHomeKit = +'open:fts_shutter_10:closed closed:fts_shutter_100:open half:fts_shutter_50:closed drive-up:fts_shutter_up@red:stop drive-down:fts_shutter_down@red:stop pct-100:fts_shutter_10:open pct-90:fts_shutter_10:closed pct-80:fts_shutter_20:closed pct-70:fts_shutter_30:closed pct-60:fts_shutter_40:closed pct-50:fts_shutter_50:closed pct-40:fts_shutter_60:open pct-30:fts_shutter_70:open pct-20:fts_shutter_80:open pct-10:fts_shutter_90:open pct-0:fts_shutter_100:closed'; + my $iconAktuell = AttrVal( $name, "devStateIcon", "kein" ); + + CommandAttr( undef, " $name devStateIcon $iconHomeKit" ) + if ( ( $aVal eq "HomeKit" ) && ( ( $iconAktuell eq $iconNormal ) || ( $iconAktuell eq "kein" ) ) ); + CommandAttr( undef, " $name devStateIcon $iconNormal" ) + if ( ( $aVal eq "normal" ) && ( ( $iconAktuell eq $iconHomeKit ) || ( $iconAktuell eq "kein" ) ) ); + } + elsif ( $aName eq "disable" ) { + if ( $aVal == 1 ) { + RemoveInternalTimer($hash); + readingsSingleUpdate( $hash, "state", "inactive", 1 ); + $hash->{helper}{DISABLED} = 1; + } + elsif ( $aVal == 0 ) { + readingsSingleUpdate( $hash, "state", "Initialized", 1 ); + $hash->{helper}{DISABLED} = 0; + } + + } + } + elsif ( $cmd eq "del" ) { + if ( $aName eq "disable" ) { + readingsSingleUpdate( $hash, "state", "Initialized", 1 ); + $hash->{helper}{DISABLED} = 0; + } + } + return undef; +} + +1; + +=pod +=item helper +=item summary Precisely control shutters/blinds which support only open/close/stop +=item summary_DE Rollladen die nur open/close/stop unterstützen päzise steuern +=begin html + + +

ROLLO

+
+ + +

Define

+ + +

Set

+ + +

Get

+ + +

Attributes

+ +
+=end html + +=begin html_DE + + +

ROLLO

+
+ +

Set

+ + +

Get

+ +

Attributes

+ +
+=end html_DE +=cut