diff --git a/fhem/CHANGED b/fhem/CHANGED index 71a9194f1..b5d089e22 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,10 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 98_Hyperion: - new "set binary restart/stop" + - non-blocking for "get configFiles", + "set configFile" and "set binary" + - ATTENTION: change from command killall to kill, + please adjust your sudoers file (if applicable) - new: 14_SD_RSL - basic support for conrad RSL - update: 14_Hideki: windsensor support added 00_SIGNALduino: Updated to v3.3.2 diff --git a/fhem/FHEM/98_Hyperion.pm b/fhem/FHEM/98_Hyperion.pm index 3a654a3c9..7433236ee 100644 --- a/fhem/FHEM/98_Hyperion.pm +++ b/fhem/FHEM/98_Hyperion.pm @@ -17,6 +17,7 @@ use Color; use DevIo; use JSON; use SetExtensions; +use Blocking; my %Hyperion_sets = ( @@ -150,6 +151,7 @@ sub Hyperion_Undef($$) { my ($hash,$name) = @_; RemoveInternalTimer($hash); + BlockingKill($hash->{helper}{RUNNING_PID}) if ($hash->{helper}{RUNNING_PID}); DevIo_CloseDev($hash); return; } @@ -168,8 +170,8 @@ sub Hyperion_list2array($$) sub Hyperion_isLocal($) { - my ($hash) = @_; - return ($hash->{IP} =~ /^(localhost|127\.0{1,3}\.0{1,3}\.0{0,2}1|::1)$/) ? 1 : undef; + my ($ip) = @_; + return ($ip =~ /^(localhost|127\.0{1,3}\.0{1,3}\.0{0,2}1|::1)$/) ? 1 : undef; } sub Hyperion_Get($@) @@ -183,6 +185,7 @@ sub Hyperion_Get($@) if (!$cmd); if ($cmd eq "configFiles") { + return "Work already/still in progress... Please wait for the current process to finish." if ($hash->{helper}{RUNNING_PID} && !$hash->{helper}{RUNNING_PID}{terminated}); Hyperion_GetConfigs($hash); } elsif ($cmd eq "devStateIcon") @@ -230,8 +233,10 @@ sub Hyperion_Read($) { my $ver = (split /V/,(split " ",$data->{hyperion_build}->[0]->{version})[0])[1]; $ver =~ s/\.//g; + $ver = $ver * 1; my $rver = $Hyperion_requiredVersion; $rver =~ s/\.//g; + $rver = $rver * 1; $error = "Your version of hyperion (detected version: ".$data->{hyperion_build}->[0]->{version}.") is not (longer) supported by this module!" if ($ver<$rver); } if ($error) @@ -383,65 +388,59 @@ sub Hyperion_Read($) sub Hyperion_GetConfigs($) { my ($hash) = @_; - return "Not connected" if (!$hash->{FD}); my $name = $hash->{NAME}; my $ip = $hash->{IP}; my $dir = AttrVal($name,"hyperionConfigDir","/etc/hyperion/"); - my $com = "ls $dir 2>/dev/null"; - my @files; - if (Hyperion_isLocal($hash)) - { - @files = Hyperion_listFilesInDir($hash,$com); - } - else + my $com = "ls $dir"; + if (!Hyperion_isLocal($ip)) { + my $ssh = qx(which ssh); + chomp $ssh; + return "SSH client could NOT be found!" if (!$ssh); my $user = AttrVal($name,"hyperionSshUser","pi"); - my $cmd = qx(which ssh); - chomp $cmd; - $cmd .= " $user\@$ip $com"; - @files = Hyperion_listFilesInDir($hash,$cmd); + $com = "$ssh $user\@$ip '$com'"; } - return "No files found on server \"$ip\" in directory \"$dir\".\nMaybe the wrong directory?\n\nIf SSH is used, has the user \"".AttrVal($name,"hyperionSshUser","pi")."\" been configured to log in without\nentering a password (http://www.linuxproblem.org/art_9.html)?" - if (@files == 0); - if (@files > 1) - { - my $configs = join(",",@files); - readingsSingleUpdate($hash,".configs",$configs,1) if (ReadingsVal($name,".configs","") ne $configs); - $attr{$name}{webCmd} = $Hyperion_webCmd_config if (AttrVal($name,"webCmd","") eq $Hyperion_webCmd); - } - else - { - AnalyzeCommandChain(undef,"deletereading $name .configs") if (defined ReadingsVal($name,".configs",undef)); - $attr{$name}{webCmd} = $Hyperion_webCmd if (AttrVal($name,"webCmd","") eq $Hyperion_webCmd_config); - return "Found just one config file.\nPlease add at least one more config file to properly use this function." - if (@files == 1); - return "No config files found!"; - } - Hyperion_GetUpdate($hash); - return "Found ".@files." config files.\nPlease refresh this page to see the result."; + Log3 $name,4,"$name: lsCmd: $com"; + $com = encode_base64($com); + $hash->{helper}{RUNNING_PID} = BlockingCall("Hyperion_ExecCmd","$name|$com","Hyperion_GetConfigs_finished"); + return "Working in background..."; } -sub Hyperion_listFilesInDir($$) +sub Hyperion_GetConfigs_finished($) { - my ($hash,$cmd) = @_; - my $name = $hash->{NAME}; - my $fh; + my ($string) = @_; + my @a = split /\|/,$string; + my $name = $a[0]; + my @files; + @files = split " ",$a[1] if ($a[1]); + my $hash = $defs{$name}; + my $ip = $hash->{IP}; + my $dir = AttrVal($name,"hyperionConfigDir","/etc/hyperion/"); + delete $hash->{helper}{RUNNING_PID}; my @filelist; - if (open(FH,"$cmd|")) + foreach (@files) { - my @files = ; - for (my $i = 0; $i < @files; $i++) - { - my $file = $files[$i]; - chomp $file; - next if ($file !~ /\w+\.config\.json$/); - $file =~ s/\.config\.json$//gm; - push @filelist,$file; - Log3 $name,4,"$name: Hyperion_listFilesInDir matching file: \"$file\""; - } - close FH; + my $file = $_; + next if ($file !~ /^([-\.\w]+)\.config\.json$/); + $file = $1; + push @filelist,$file; + Log3 $name,4,"$name: matching config file: \"$_\""; } - return @filelist; + if (@filelist) + { + my $configs = join(",",@filelist); + readingsSingleUpdate($hash,".configs",$configs,0); + CommandAttr(undef,"$name webCmd $Hyperion_webCmd_config") if (AttrVal($name,"webCmd","") eq $Hyperion_webCmd && @filelist > 1); + CommandAttr(undef,"$name webCmd $Hyperion_webCmd") if (AttrVal($name,"webCmd","") eq $Hyperion_webCmd_config && @filelist < 2); + } + else + { + CommandDeleteReading(undef,"$name .configs") if (ReadingsVal($name,".configs","")); + CommandAttr(undef,"$name webCmd $Hyperion_webCmd") if (AttrVal($name,"webCmd","") eq $Hyperion_webCmd_config); + Log3 $name,3,"$name: No files found on server \"$ip\" in directory \"$dir\".\nMaybe the wrong directory?\n\nIf SSH is used, has the user \"".AttrVal($name,"hyperionSshUser","pi")."\" been configured to log in without\nentering a password (http://www.linuxproblem.org/art_9.html)?"; + } + Hyperion_GetUpdate($hash); + return; } sub Hyperion_GetUpdate(@) @@ -460,6 +459,97 @@ sub Hyperion_GetUpdate(@) return; } +sub Hyperion_ExecCmd($) +{ + my ($string) = @_; + my @a = split /\|/,$string; + my $name = $a[0]; + my $cmd = decode_base64($a[1]); + my $hash = $defs{$name}; + my @qx = qx($cmd); + my @ret; + my $re = ""; + foreach (@qx) + { + chomp $_; + $_ =~ s/^[\s\t]{1,}/ /; + push @ret,$_; + } + $re .= join " ",@ret if (@ret); + return "$name|$re"; +} + +sub Hyperion_Kill_finished($) +{ + my ($string) = @_; + my @a = split /\|/,$string; + my $name = $a[0]; + my $error = $a[1]; + my $hash = $defs{$name}; + delete $hash->{helper}{RUNNING_PID}; + if ($error) + { + Log3 $name,3,"$name: Not able to stop Hyperion! Error: $error"; + readingsSingleUpdate($hash,"lastError",$error,1); + } + else + { + Log3 $name,3,"$name: Hyperion has been stopped"; + RemoveInternalTimer($hash); + DevIo_Disconnected($hash); + } + return undef; +} + +sub Hyperion_Restart($) +{ + my ($string) = @_; + my @a = split /\|/,$string; + my $name = $a[0]; + my $error = $a[1]; + my $hash = $defs{$name}; + delete $hash->{helper}{RUNNING_PID}; + if ($error) + { + Log3 $name,3,"$name: Not able to stop Hyperion! Error: $error"; + readingsSingleUpdate($hash,"lastError",$error,1); + } + else + { + my $cmd = $hash->{helper}{startCmd}; + $hash->{helper}{RUNNING_PID} = BlockingCall("Hyperion_ExecCmd","$name|$cmd","Hyperion_Restart_finished"); + } + return undef; +} + +sub Hyperion_Restart_finished($) +{ + my ($string) = @_; + my @a = split /\|/,$string; + my $name = $a[0]; + my $error = $a[1]; + my $hash = $defs{$name}; + delete $hash->{helper}{RUNNING_PID}; + my $file = $hash->{helper}{configFile}; + delete $hash->{helper}{configFile}; + delete $hash->{helper}{startCmd}; + if ($error) + { + Log3 $name,3,"$name: Hyperion could not be restarted! Error: $error"; + readingsSingleUpdate($hash,"lastError",$error,1); + } + else + { + Log3 $name,3,"$name: Hyperion restarted with configFile $file"; + RemoveInternalTimer($hash); + DevIo_Disconnected($hash); + $file =~ s/\.config\.json$//; + readingsSingleUpdate($hash,"configFile",$file,1); + InternalTimer(gettimeofday() + 3,"Hyperion_OpenDev",$hash); + } + return undef; +} + sub Hyperion_Set($@) { my ($hash,$name,@aa) = @_; @@ -473,7 +563,7 @@ sub Hyperion_Set($@) if (ReadingsVal($name,".configs","")) { $Hyperion_sets_local{configFile} = ReadingsVal($name,".configs",""); - $attr{$name}{webCmd} = $Hyperion_webCmd_config if (AttrVal($name,"webCmd","") eq $Hyperion_webCmd); + $Hyperion_sets_local{binary} = "restart,stop"; } $Hyperion_sets_local{adjustRed} = "textField" if (ReadingsVal($name,"adjustRed","")); $Hyperion_sets_local{adjustGreen} = "textField" if (ReadingsVal($name,"adjustGreen","")); @@ -495,55 +585,70 @@ sub Hyperion_Set($@) Log3 $name,4,"$name: Hyperion_Set cmd: $cmd"; Log3 $name,4,"$name: Hyperion_Set value: $value" if ($value); Log3 $name,4,"$name: Hyperion_Set duration: $duration, priority: $priority" if ($cmd =~ /^rgb|dim|dimUp|dimDown|effect$/); - if ($cmd eq "configFile") + if ($cmd =~ /^configFile|binary$/) { - $value = $value.".config.json"; - my $confdir = AttrVal($name,"hyperionConfigDir","/etc/hyperion/"); + return "Work already/still in progress... Please wait for the current process to finish." if ($hash->{helper}{RUNNING_PID} && !$hash->{helper}{RUNNING_PID}{terminated}); my $binpath = AttrVal($name,"hyperionBin","/usr/bin/hyperiond"); my $bin = (split "/",$binpath)[scalar(split "/",$binpath) - 1]; $bin =~ s/\.sh$// if ($bin =~ /\.sh$/); + my $confdir = AttrVal($name,"hyperionConfigDir","/etc/hyperion/"); my $user = AttrVal($name,"hyperionSshUser","pi"); my $ip = $hash->{IP}; my $sudo = ($user eq "root" || int AttrVal($name,"hyperionNoSudo",0) == 1) ? "" : "sudo "; - my $command = $sudo."killall $bin; sleep 1; ".$sudo."$binpath $confdir$value > /dev/null 2>&1 &"; - my $status; - my $fh; - if (Hyperion_isLocal($hash)) + my $kill = $sudo."kill `pidof $bin`"; + my $ssh; + if (!Hyperion_isLocal($ip)) { - if (open(FH,"$command|")) - { - $status = ; - close FH; - } + $ssh = qx(which ssh); + chomp $ssh; + return "SSH client could NOT be found!" if (!$ssh); + } + my $com = Hyperion_isLocal($ip)?"":"$ssh $user\@$ip '"; + if ($cmd eq "binary") + { + return "Value of $cmd has to be 'stop' or 'restart'" if ($value !~ /^(stop|restart)$/); } else { - my $com = qx(which ssh); - chomp $com; - $com .= " $user\@$ip '$command'"; - if (open(FH,"$com|")) + return "Value of $cmd must be given and must be an available config file!" if (!$value || !grep(/^$value$/,split /,/,ReadingsVal($name,".configs",""))); + } + if ($cmd eq "binary" && $value eq "stop") + { + $com .= $kill; + $com .= Hyperion_isLocal($ip)?"":"'"; + Log3 $name,4,"$name: stopCmd: $com"; + $com = encode_base64($com); + $hash->{helper}{RUNNING_PID} = BlockingCall("Hyperion_ExecCmd","$name|$com","Hyperion_Kill_finished"); + } + elsif (($cmd eq "binary" && $value eq "restart") || $cmd eq "configFile") + { + my $file; + if ($value eq "restart") { - $status = ; - close FH; + $file = ReadingsVal($name,"configFile","")?ReadingsVal($name,"configFile",""):ReadingsVal($name,".configs","")?(split /,/,ReadingsVal($name,".configs",""))[0]:""; + return "No restart possible because no configFile is available." if (!$file); } + else + { + $file = $value; + } + my $start = $com; + $file .= ".config.json"; + $com .= $kill; + $start .= "$sudo$binpath $confdir".$file." > /dev/null 2>&1 &"; + if (!Hyperion_isLocal($ip)) + { + $com .= "'"; + $start .= "'"; + } + Log3 $name,4,"$name: stopCmd: $com"; + Log3 $name,4,"$name: startCmd: $start"; + $com = encode_base64($com); + $hash->{helper}{configFile} = $file; + $hash->{helper}{startCmd} = encode_base64($start); + $hash->{helper}{RUNNING_PID} = BlockingCall("Hyperion_ExecCmd","$name|$com","Hyperion_Restart"); } - if (!$status) - { - Log3 $name,4,"$name: restarted Hyperion with $binpath $confdir$value"; - $value =~ s/\.config\.json$//; - readingsSingleUpdate($hash,"configFile",$value,1); - return; - } - else - { - Log3 $name,4,"$name: NOT restarted Hyperion with $binpath $confdir$value, status: $status"; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash,"lastError",$status); - readingsBulkUpdate($hash,"serverResponse","ERROR"); - readingsBulkUpdate($hash,"state","ERROR"); - readingsEndUpdate($hash,1); - return "$name NOT restarted Hyperion with $binpath $confdir$value, status: $status"; - } + return; } elsif ($cmd eq "rgb") { @@ -752,7 +857,7 @@ sub Hyperion_Set($@) my $effs = AttrVal($name,"hyperionCustomEffects",""); $effs .= "\r\n" if ($effs); $effs .= '{"name":"'.$value.'","oname":"'.$eff.'","args":'.ReadingsVal($name,"effectArgs","").'}'; - $attr{$name}{hyperionCustomEffects} = $effs; + CommandAttr(undef,"$name hyperionCustomEffects $effs"); return; } elsif ($cmd eq "reopen") @@ -775,7 +880,7 @@ sub Hyperion_Attr(@) my ($cmd,$name,$attr_name,$attr_value) = @_; my $hash = $defs{$name}; my $err; - my $local = Hyperion_isLocal($hash); + my $local = Hyperion_isLocal($hash->{IP}); if ($cmd eq "set") { if ($attr_name eq "hyperionBin") @@ -963,6 +1068,12 @@ sub Hyperion_devStateIcon($;$)
    define Ambilight Hyperion 192.168.1.4 19444 10
+

+ To change config files on your running Hyperion server or to stop/restart your Hyperion server you have to put the following code into your sudoers file (/etc/sudoers) (visudo): +

+
    + fhem ALL=(ALL) NOPASSWD:/usr/bin/hyperiond,/bin/kill +

set <required> [optional]

@@ -988,6 +1099,11 @@ sub Hyperion_devStateIcon($;$) adjust each color of red separately (comma separated) (R,G,B)
values from 0 to 255 in steps of 1 +
  • + binary <restart/stop>
    + restart or stop the hyperion binary
    + only available after successful "get <name> configFiles" +
  • blacklevel <0.00,0.00,0.00>
    adjust blacklevel of each color separately (comma separated) (R,G,B)
    @@ -1110,7 +1226,9 @@ sub Hyperion_devStateIcon($;$)
  • configFiles
    get the available config files in directory from attribute hyperionConfigDir
    - Will only work properly if at least two config files are found. File names must have no spaces and must end with .config.json . + File names must have no spaces and must end with .config.json .
    + For non-local Hyperion servers you have to configure passwordless SSH login for the user running fhem to the Hyperion server host (http://www.linuxproblem.org/art_9.html), with attribute hyperionSshUser you can set the SSH user for login.
    + Please watch the log for possible errors while getting config files.
  • devStateIcon