######################################################################################## # $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. # # CHANGELOG: # 1.405: Fixed an issue with external driving (when already at position) # 1.404: Hint in Commandref regarding position->pct # 1.403: Loglevel from 3 to 5 for few messages # Rollo should only drive 10 steps in "force" mode for up/down ######################################################################################## package main; use strict; use warnings; my $version = "1.403"; 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 $desiredPos; 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; $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 $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 ""; } #set desiredPos to avoid "uninitialized" message later (happens with "blocked" - KernSani 14.01.2019 $desiredPos = ReadingsNum( $name, "desired_pct", 0 ) unless defined($desiredPos); 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, 5, "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 ); } # Wenn drivetype "extern" müssen wir drive_type wieder zurücksetzen - KernSani 27.01.2019 elsif ( ReadingsVal( $name, "drive-type", "undef" ) eq "extern" ) { readingsSingleUpdate( $hash, "drive-type", "na", 1 ); } 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} ) ) { $state = $rhash{$newpos}; } else { #ich kenne keinen Text für die pct, also als pct-nn anzeigen $newpos = 100 - $newpos if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" ); $state = "pct-$newpos"; } Log3 $name, 4, "ROLLO ($name) updating state to $state"; 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"; my $cmd = ReadingsVal( $name, "command", "stop" ); if ( $cmd eq "up" or $cmd eq "down" ) { $steps = 10; } else { $steps = 100; } } } if ( !defined($time) ) { Log3 $name, 2, "ROLLO ($name) ERROR: missing attribute secondsUp or secondsDown"; $time = 60; } my $drivetime = $time * $steps / 100; Log3 $name, 5, "ROLLO ($name) netto drive time = $drivetime"; # 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