2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-02-26 10:34:52 +00:00

30_tradfri.pm: new module

CoProcess.pm: new file


git-svn-id: https://svn.fhem.de/fhem/trunk@18425 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
justme-1968 2019-01-26 16:45:12 +00:00
parent e013121eb5
commit bd285f94a7
4 changed files with 1102 additions and 3 deletions

View File

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # 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. # Do not insert empty lines here, update check depends on it.
- new: 30_tradfri: initial release
- new: 42_npmjs: Module to update Node.js modules via NPM package manager - new: 42_npmjs: Module to update Node.js modules via NPM package manager
- feature: 98_Hyperion: add set active/inactive - feature: 98_Hyperion: add set active/inactive
- bugfix: 22_HOMEMODE: fix zero devider in HOMEMODE_ContactOpenCheck - bugfix: 22_HOMEMODE: fix zero devider in HOMEMODE_ContactOpenCheck

679
fhem/FHEM/30_tradfri.pm Normal file
View File

@ -0,0 +1,679 @@
# $Id$
package main;
use strict;
use warnings;
use CoProcess;
#require "30_HUEBridge.pm";
require "$attr{global}{modpath}/FHEM/30_HUEBridge.pm";
use JSON;
use Data::Dumper;
use POSIX;
use Socket;
use vars qw(%modules);
use vars qw(%defs);
use vars qw(%attr);
use vars qw($readingFnAttributes);
use vars qw($FW_ME);
sub Log($$);
sub Log3($$$);
sub
tradfri_Initialize($)
{
my ($hash) = @_;
$hash->{ReadFn} = "tradfri_Read";
$hash->{WriteFn} = "tradfri_Write";
$hash->{DefFn} = "tradfri_Define";
$hash->{NotifyFn} = "tradfri_Notify";
$hash->{UndefFn} = "tradfri_Undefine";
$hash->{DelayedShutdownFn} = "tradfri_DelayedShutdown";
$hash->{ShutdownFn} = "tradfri_Shutdown";
$hash->{SetFn} = "tradfri_Set";
$hash->{GetFn} = "tradfri_Get";
$hash->{AttrFn} = "tradfri_Attr";
$hash->{AttrList} = "tradfriFHEM-cmd ".
"tradfriFHEM-params ".
"tradfriFHEM-securityCode ".
"tradfriFHEM-sshHost tradfriFHEM-sshUser ".
"disable:1 disabledForIntervals ".
"createGroupReadings:1,0 ".
$readingFnAttributes;
}
#####################################
sub
tradfri_AttrDefaults($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
}
sub
tradfri_Define($$)
{
my ($hash, $def) = @_;
my @a = split("[ \t][ \t]*", $def);
return "Usage: define <name> tradfri" if(@a != 2);
my $name = $a[0];
$hash->{NAME} = $name;
my $d = $modules{$hash->{TYPE}}{defptr};
return "$hash->{TYPE} device already defined as $d->{NAME}." if( defined($d) && $name ne $d->{NAME} );
$modules{$hash->{TYPE}}{defptr} = $hash;
tradfri_AttrDefaults($hash);
$hash->{NOTIFYDEV} = "global";
if( !AttrVal($name, 'devStateIcon', undef ) ) {
CommandAttr(undef, "$name createGroupReadings 0");
CommandAttr(undef, "$name stateFormat tradfri-fhem");
CommandAttr(undef, "$name devStateIcon stopped:control_home\@red:start stopping:control_on_off\@orange running.*:control_on_off\@green:stop")
}
$hash->{CoProcess} = { name => 'tradfri-fhem',
cmdFn => 'tradfri_getCmd',
};
$hash->{helper}{scenes} = {} if( !$hash->{helper}{scenes} );
if( $init_done ) {
CoProcess::start($hash);
} else {
$hash->{STATE} = 'active';
}
return undef;
}
sub
tradfri_Notify($$)
{
my ($hash,$dev) = @_;
return if($dev->{NAME} ne "global");
return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
CoProcess::start($hash);
return undef;
}
sub
tradfri_Undefine($$)
{
my ($hash, $name) = @_;
CoProcess::terminate($hash);
delete $modules{$hash->{TYPE}}{defptr};
return undef;
}
sub
tradfri_DelayedShutdown($)
{
my ($hash) = @_;
if( $hash->{PID} ) {
$hash->{shutdown} = 1;
$hash->{shutdown} = $hash->{CL} if( $hash->{CL} );
$hash->{reason} = 'shutdown';
CoProcess::stop($hash);
return 1;
}
return undef;
}
sub
tradfri_Shutdown($)
{
my ($hash) = @_;
CoProcess::terminate($hash);
delete $modules{$hash->{TYPE}}{defptr};
return undef;
}
sub
tradfri_processEvent($$) {
my ($hash,$decoded) = @_;
my $name = $hash->{NAME};
my $id = $decoded->{id} ;
if( $decoded->{r} eq 'scene' ) {
if( $decoded->{t} eq 'remove' ) {
delete $hash->{helper}{scenes}{$id};
Log3 $name, 4, "$name: deleted scene $id";
} else {
Log3 $name, 4, "$name: ". ($hash->{helper}{scenes}{$id}?'updated':'added') ." scene $id";
$hash->{helper}{scenes}{$id} = $decoded;
}
return;
}
my $code = '';
$code = $name ."-". $id if( $decoded->{r} eq 'lights' );
$code = $name ."-G". $id if( $decoded->{r} eq 'group' );
$code = $name ."-S". $id if( $decoded->{r} eq 'sensor' );
my $chash = $modules{HUEDevice}{defptr}{$code};
if( $decoded->{t} eq 'remove' ) {
if( $chash ) {
fhem( "delete $chash->{NAME} " );
}
return;
}
if( defined($chash) ) {
HUEDevice_Parse($chash,$decoded);
HUEBridge_updateGroups($hash, $chash->{ID}) if( !$chash->{helper}{devtype} );
} else {
my $group;
my $cname;
my $define;
if( $decoded->{r} eq 'lights' ) {
$group = 'HUEDevice';
$cname = "HUEDevice" . $id;
#$cname = $name ."_". $cname if( $hash->{helper}{count} );
$define= "$cname HUEDevice $id IODev=$name";
} elsif( $decoded->{r} eq 'group' ) {
$group = 'HUEGroup';
$cname = "HUEGroup" . $id;
#$cname = $name ."_". $cname if( $hash->{helper}{count} );
$define= "$cname HUEDevice group $id IODev=$name";
} elsif( $decoded->{r} eq 'sensor' ) {
$group = 'HUESensor';
$cname = "HUESensor" . $id;
#$cname = $name ."_". $cname if( $hash->{helper}{count} );
$define= "$cname HUEDevice sensor $id IODev=$name";
}
if( $define ) {
Log3 $name, 4, "$name: create new device '$cname' for address '$id'";
Log3 $name, 5, "$name: $define";
if( my $ret = CommandDefine(undef,$define) ) {
Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $ret";
} else {
CommandAttr(undef,"$cname alias ".$decoded->{name});
CommandAttr(undef,"$cname room Tradfri");
#CommandAttr(undef,"$cname IODev $name");
CommandAttr(undef, "$name createGroupReadings 1") if( $decoded->{r} eq 'group' );
HUEDeviceSetIcon($cname);
$defs{$cname}{helper}{fromAutocreate} = 1 ;
CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
my $chash = $modules{HUEDevice}{defptr}{$code};
if( defined($chash) ) {
HUEDevice_Parse($chash,$decoded);
HUEBridge_updateGroups($hash, $chash->{ID}) if( !$chash->{helper}{devtype} );
}
}
} else {
Log3 $name, 4, "$name: message for unknow device received: $code: ". Dumper $decoded;
}
}
}
sub
tradfri_Read($) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $buf = CoProcess::readFn($hash);
return undef if( !$buf );
my $data = $hash->{helper}{PARTIAL};
$data .= $buf;
while($data =~ m/\n/) {
($buf,$data) = split("\n", $data, 2);
Log3 $name, 5, "$name: read: $buf";
if( $buf =~ m/^\*\*\* ([^\s]+) (.+)/ ) {
my $service = $1;
my $message = $2;
if( $service eq 'FHEM:' ) {
if( $message =~ m/^connection failed(, (.*))?/ ) {
my $reason = $2;
$hash->{reason} = 'failed to connect to gateway';
$hash->{reason} .= ": $reason" if( $reason );
if( $reason eq 'credentials wrong' ) {
fhem( "deletereading $name identity" );
fhem( "deletereading $name psk" );
CoProcess::start($hash);
} elsif( $reason eq 'credentials missing' ) {
CoProcess::stop($hash);
} elsif( $reason ) { #secret wrong
CoProcess::stop($hash);
} else {
CoProcess::start($hash);
}
} elsif( $message =~ m/^identity: (.*)/ ) {
my $identity = $1;
readingsSingleUpdate($hash, 'identity', tradfri_encrypt($identity), 1 );
} elsif( $message =~ m/^psk: (.*)/ ) {
my $psk = $1;
readingsSingleUpdate($hash, 'psk', tradfri_encrypt($psk), 1 );
} else {
Log3 $name, 4, "$name: unhandled message: $message";
}
}
} elsif( $buf =~ m/this is tradfri-fhem (.*)/ ) {
$hash->{'tradfri-fhem version'} = $1;
} elsif( $buf =~ m/^\{.*\}/ ) {
if( my $decoded = eval { JSON->new->utf8(0)->decode($buf) } ) {
tradfri_processEvent($hash,$decoded);
} else {
Log3 $name, 2, "$name: json error: $@ in $buf";
}
} else {
Log3 $name, 4, "$name: $buf";
}
}
$hash->{PARTIAL} = $data;
return undef;
}
sub
tradfri_Write($@)
{
my ($hash,$chash,$cname,$id,$obj)= @_;
my $name = $hash->{NAME};
return undef if( IsDisabled($name) );
#$id =~ s'/.*''g;
#$obj->{id} = $id;
$id = $1 if( $id =~ m/^G(\d+)/ );
$id = $1 if( $id =~ m/^(\d+)/ );
$obj->{id} = $id;
$obj->{t} = 'lights';
$obj->{t} = 'group' if( $chash->{helper}{devtype} eq 'G' );
if( $hash->{FH} ) {
my $encoded = encode_json($obj);
Log3 $name, 5, "$name: writing: $encoded";
$encoded .= "\n";
syswrite( $hash->{FH}, $encoded );
} else {
Log3 $name, 3, "$name: not connected";
}
return undef;
}
sub
tradfri_getCmd($) {
my ($hash) = @_;
my $name = $hash->{NAME};
return undef if( !$init_done );
my $ssh_cmd;
if( my $host = AttrVal($name, 'tradfriFHEM-sshHost', undef ) ) {
my $ssh = qx( which ssh ); chomp( $ssh );
if( my $user = AttrVal($name, 'tradfriFHEM-sshUser', undef ) ) {
$ssh_cmd = "$ssh $host -u $user";
} else {
$ssh_cmd = "$ssh $host";
}
Log3 $name, 3, "$name: using ssh cmd $ssh_cmd";
}
my $cmd;
if( $ssh_cmd ) {
$cmd = AttrVal( $name, "tradfriFHEM-cmd", qx( $ssh_cmd which tradfri-fhem ) );
} else {
$cmd = AttrVal( $name, "tradfriFHEM-cmd", qx( which tradfri-fhem ) );
}
chomp( $cmd );
if( !$ssh_cmd && !(-X $cmd) ) {
my $msg = "tradfri-fhem not installed. install with 'sudo npm install -g tradfri-fhem'.";
$msg = "$cmd does not exist" if( $cmd );
return (undef, $msg);
}
$cmd = "$ssh_cmd $cmd" if( $ssh_cmd );
if( my $security_code = AttrVal($name, 'tradfriFHEM-securityCode', undef ) ) {
$cmd .= ' -s '. tradfri_decrypt($security_code);
} else {
my $msg = 'security code missing';
return (undef, $msg);
}
if( my $identity = ReadingsVal($name, 'identity', undef ) ) {
$cmd .= " -i ". tradfri_decrypt($identity) ;
}
if( my $psk = ReadingsVal($name, 'psk', undef ) ) {
$cmd .= " -p ". tradfri_decrypt($psk) ;
}
if( my $params = AttrVal($name, 'tradfriFHEM-params', undef ) ) {
$cmd .= " $params";
}
if( AttrVal( $name, 'verbose', 3 ) == 5 ) {
Log3 $name, 2, "$name: starting tradfri-fhem: $cmd";
} else {
my $msg = $cmd;
$msg =~ s/-s\s+[^\s]+/-s sssss/g;
$msg =~ s/-i\s+[^\s]+/-i iiiii/g;
$msg =~ s/-p\s+[^\s]+/-p ppppp/g;
Log3 $name, 2, "$name: starting tradfri-fhem: $msg";
}
return $cmd;
}
sub
tradfri_Set($$@)
{
my ($hash, $name, $cmd, @args) = @_;
my $list = "";
if( $cmd eq 'scene' ) {
return "usage: scene <id>" if( @args != 1 );
my $id = $args[0];
if( !defined($hash->{helper}{scenes}{$id}) ) {
foreach my $key ( keys %{$hash->{helper}{scenes}} ) {
if( $id eq $hash->{helper}{scenes}{$key}{name} ) {
$id = $key;
last;
}
}
return "no such scene" if( !defined($hash->{helper}{scenes}{$id}) );
}
my $scene = $hash->{helper}{scenes}{$id};
my $obj = { 'sceneId' => 0+$id };
my $code = $name ."-G". $scene->{group};
my $chash = $modules{HUEDevice}{defptr}{$code};
tradfri_Write($hash, $chash, $chash->{NAME}, $chash->{ID} , $obj);
return undef;
} elsif( $cmd eq 'statusRequest' ) {
#unused
return undef;
}
my $scenes;
foreach my $key ( sort {$a cmp $b} keys %{$hash->{helper}{scenes}} ) {
$scenes .=',' if( $scenes );
my $name = $hash->{helper}{scenes}{$key}{name};
$name =~ s/ /#/g;
$scenes .= $name;
}
if( $scenes ) {
$list .= " " if( $list );
$list .= " scene:$scenes";
}
return CoProcess::setCommands($hash, $list, $cmd, @args);
}
sub
tradfri_Get($$@)
{
my ($hash, $name, $cmd) = @_;
my $list = 'scenes:noArg';
if( $cmd eq 'scenes' ) {
my $ret;
foreach my $key ( sort {$a cmp $b} keys %{$hash->{helper}{scenes}} ) {
my $scene = $hash->{helper}{scenes}{$key};
my $group = $scene->{group};
my $code = $name ."-G". $scene->{group};
if( my $chash = $modules{HUEDevice}{defptr}{$code} ) {
$group = AttrVal( $chash->{NAME}, 'alias', $group );
}
$ret .= sprintf( "%-20s %-20s %-20s", $key, $group, $scene->{name} );
$ret .= sprintf( " %s\n", join( ",", @{$scene->{lights}} ) );
}
if( $ret ) {
my $header = sprintf( "%-20s %-20s %-20s", "ID", "GROUP", "NAME" );
$header .= sprintf( " %s\n", "LIGHTS" );
$ret = $header . $ret;
}
return $ret;
}
return "Unknown argument $cmd, choose one of $list";
}
sub
tradfri_Parse($$;$)
{
my ($hash,$data,$peerhost) = @_;
my $name = $hash->{NAME};
}
sub
tradfri_encrypt($)
{
my ($decoded) = @_;
my $key = getUniqueId();
return "" if( !$decoded );
return $decoded if( $decoded =~ /^crypt:(.*)/ );
my $encoded;
for my $char (split //, $decoded) {
my $encode = chop($key);
$encoded .= sprintf("%.2x",ord($char)^ord($encode));
$key = $encode.$key;
}
return 'crypt:'. $encoded;
}
sub
tradfri_decrypt($)
{
my ($encoded) = @_;
my $key = getUniqueId();
return "" if( !$encoded );
$encoded = $1 if( $encoded =~ /^crypt:(.*)/ );
my $decoded;
for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) {
my $decode = chop($key);
$decoded .= chr(ord($char)^ord($decode));
$key = $decode.$key;
}
return $decoded;
}
sub
tradfri_Attr($$$)
{
my ($cmd, $name, $attrName, $attrVal) = @_;
my $orig = $attrVal;
my $hash = $defs{$name};
if( $attrName eq 'disable' ) {
my $hash = $defs{$name};
if( $cmd eq "set" && $attrVal ne "0" ) {
$attrVal = 1;
CoProcess::stop($hash);
} else {
$attr{$name}{$attrName} = 0;
CoProcess::start($hash);
}
} elsif( $attrName eq 'disabledForIntervals' ) {
$attr{$name}{$attrName} = $attrVal;
CoProcess::start($hash);
} elsif( $attrName eq 'tradfriFHEM-params' ) {
$attr{$name}{$attrName} = $attrVal;
CoProcess::start($hash);
} elsif( $attrName eq 'tradfriFHEM-sshHost' ) {
$attr{$name}{$attrName} = $attrVal;
CoProcess::start($hash);
} elsif( $attrName eq 'tradfriFHEM-sshUser' ) {
$attr{$name}{$attrName} = $attrVal;
CoProcess::start($hash);
} elsif( $attrName eq 'tradfriFHEM-securityCode' ) {
if( $cmd eq "set" && $attrVal ) {
$attrVal = tradfri_encrypt($attrVal);
}
$attr{$name}{$attrName} = $attrVal;
CoProcess::start($hash);
if( $cmd eq "set" && $orig ne $attrVal ) {
$attr{$name}{$attrName} = $attrVal;
return "stored obfuscated security code";
}
}
if( $cmd eq 'set' ) {
if( $orig ne $attrVal ) {
$attr{$name}{$attrName} = $attrVal;
return "stored modified value";
}
} else {
delete $attr{$name}{$attrName};
RemoveInternalTimer($hash);
InternalTimer(gettimeofday(), "tradfri_AttrDefaults", $hash, 0);
}
return;
}
1;
=pod
=item summary Module to control the FHEM/tradfri integration
=item summary_DE Modul zur Konfiguration der FHEM/tradfri Integration
=begin html
<a name="tradfri"></a>
<h3>tradfri</h3>
<ul>
Module to control the integration of IKEA tradfri devices with FHEM.<br><br>
Notes:
<ul>
<li>JSON has to be installed on the FHEM host.</li>
</ul>
<a name="tradfri_Set"></a>
<b>Set</b>
<ul>
<li>scene &lt;name|id&gt;<br>
</li>
</ul>
<a name="tradfri_Get"></a>
<b>Get</b>
<ul>
<li>scenes<br>
</li>
</ul>
<a name="tradfri_Attr"></a>
<b>Attr</b>
<ul>
<li>tradfriFHEM-securityCode<br>
the security code on the back of the gateway</li>
<li>tradfriFHEM-cmd<br>
The command to use as tradfri-fhem</li>
<li>tradfriFHEM-params<br>
Additional tradfri-fhem cmdline params.</li>
</ul>
</ul><br>
=end html
=cut

416
fhem/FHEM/CoProcess.pm Normal file
View File

@ -0,0 +1,416 @@
# $Id$
use strict;
use vars qw(%cmds);
use vars qw(%defs);
use vars qw($init_done);
use vars qw(%selectlist);
sub CoProcess::Info($$@);
my %hash = (
Fn => "CoProcess::Info",
Hlp => ",show info about processes started by CoProcess"
);
$cmds{coprocessinfo} = \%hash;
sub
CoProcess_Initialize() {
}
package CoProcess;
use POSIX;
use Socket;
sub
Info($$@) {
my @ret;
foreach my $d (keys %main::defs) {
my $h =$main::defs{$d};
next if( !defined($h->{CoProcess}) );
my $line;
$line = sprintf( "%-15s %-15s %-35s %-19s %-19s %8s %s", $h->{NAME}, $h->{CoProcess}{name}, $h->{CoProcess}{state}, $h->{LAST_START}, $h->{LAST_STOP}, $h->{PID}, $h->{logfile} );
push @ret, $line;
}
unshift @ret, sprintf( "\n%-15s %-15s %-35s %-19s %-19s %8s %s", 'DEVICE', 'NAME', 'state', 'LAST START', 'LAST STOP', 'PID', 'logfile' ) if( @ret );
push @ret, "No CoProcesses are currently used" if(!@ret);
return join("\n", @ret) ."\n";
}
sub
callFn($$) {
my ($hash,$n,@params) = @_;
my $name = $hash->{NAME};
if( !defined($hash->{CoProcess}) || !defined($hash->{CoProcess}{$n}) ) {
main::Log3 $name, 4, "$name: CoProcess: no such function: $n";
return undef;
}
my $fn = "main::$hash->{CoProcess}{$n}";
if(wantarray) {
no strict "refs";
my @ret = &{$fn}($hash, @params);
use strict "refs";
return @ret;
} else {
no strict "refs";
my $ret = &{$fn}($hash, @params);
use strict "refs";
return $ret;
}
}
sub
openLogfile($;$) {
my ($hash,$logfile) = @_;
my $name = $hash->{NAME};
closeLogfile($hash) if( $hash->{log} );
if( !$logfile ) {
$logfile = $hash->{logfile};
if( !$logfile ) {
$logfile = main::AttrVal($name, 'CoProc-log', undef);
$hash->{logfile} = $logfile if( $logfile );
}
if( $logfile && $logfile ne 'FHEM' ) {
$hash->{logfile} = $logfile;
my @t = localtime(time());
$logfile = main::ResolveDateWildcards($logfile, @t);
}
}
if( $logfile && $logfile ne 'FHEM' ) {
$hash->{currentlogfile} = $logfile;
main::HandleArchiving($hash);
if( open( my $fh, ">>$logfile") ) {
$fh->autoflush(1);
$hash->{log} = $fh;
main::Log3 $name, 3, "$name: using logfile: $logfile";
} else {
main::Log3 $name, 2, "$name: failed to open logile: $logfile: $!";
}
}
main::Log3 $name, 3, "$name: using FHEM logfile" if( !$hash->{log} );
}
sub
closeLogfile($) {
my ($hash) = @_;
close($hash->{log}) if( $hash->{log} );
delete $hash->{log};
delete $hash->{currentlogfile};
}
# start co process
sub
start($;$) {
my ($hash,$cmd) = @_;
my $name = $hash->{NAME};
my $error;
($cmd,$error) = callFn($hash,'cmdFn') if( !$cmd );
if( $error ) {
$hash->{CoProcess}{state} = "stopped; $error";
main::readingsSingleUpdate($hash, $hash->{CoProcess}{name}, $hash->{CoProcess}{state}, 1 ) if( $hash->{CoProcess}{name} );
main::Log3 $name, 2, "$name: $error";
}
return undef if( !$cmd );
return undef if( !$main::init_done );
return undef if( !$hash->{CoProcess} );
return undef if( main::IsDisabled($name) );
if( $hash->{PID} ) {
$hash->{restart} = 1;
stop($hash);
return undef;
}
delete $hash->{restart};
my ($child, $parent);
# SOCK_NONBLOCK ?
if( socketpair($child, $parent, AF_UNIX, SOCK_STREAM, PF_UNSPEC) ) {
$child->autoflush(1);
$parent->autoflush(1);
my $pid = main::fhemFork();
if( !defined($pid) ) {
close $parent;
close $child;
main::Log3 $name, 1, "$name: Cannot fork: $!";
return;
}
if( $pid ) {
close $parent;
$hash->{STARTS}++;
$hash->{FH} = $child;
$hash->{FD} = fileno($child);
$hash->{PID} = $pid;
$main::selectlist{$name} = $hash;
main::Log3 $name, 3, "$name: starting";
$hash->{LAST_START} = main::FmtDateTime( time() );
$cmd = (split( ' ', $cmd, 2 ))[0];
$hash->{CoProcess}{state} = "running $cmd";
main::readingsSingleUpdate($hash, $hash->{CoProcess}{name}, $hash->{CoProcess}{state}, 1 ) if( $hash->{CoProcess}{name} );
openLogfile($hash);
} else {
close $child;
close STDIN;
close STDOUT;
close STDERR;
my $fn = $parent->fileno();
open(STDIN, "<&$fn") or die "can't redirect STDIN $!";
open(STDOUT, ">&$fn") or die "can't redirect STDOUT $!";
open(STDERR, ">&$fn") or die "can't redirect STDERR $!";
STDOUT->autoflush(1);
STDERR->autoflush(1);
close $parent;
exec split( ' ', $cmd ) or main::Log3 $name, 1, "exec failed";
main::Log3 $name, 1, "set the alexaFHEM-cmd attribut to: <path>/alexa-fhem";
POSIX::_exit(0);;
}
} else {
main::Log3 $name, 3, "$name: socketpair failed";
main::InternalTimer(time()+20, "CoProcess::start", $hash, 0);
}
}
# stop coprocess
sub
stop($) {
my ($hash) = @_;
my $name = $hash->{NAME};
main::RemoveInternalTimer($hash);
return undef if( !$hash->{PID} );
if( $hash->{PID} ) {
kill( SIGTERM, $hash->{PID} );
#kill( SIGkILL, $hash->{PID} );
# waitpid($hash->{PID}, 0);
# delete $hash->{PID};
}
$hash->{CoProcess}{state} = 'stopping';
main::readingsSingleUpdate($hash, $hash->{CoProcess}{name}, $hash->{CoProcess}{state}, 1 ) if( $hash->{CoProcess}{name} );
main::InternalTimer(time()+5, "CoProcess::terminate", $hash, 0);
}
# kill co process imediately
sub
terminate($) {
my ($hash) = @_;
my $name = $hash->{NAME};
main::RemoveInternalTimer($hash);
return undef if( !$hash->{PID} );
return undef if( !$hash->{FD} );
kill( SIGKILL, $hash->{PID} );
waitpid($hash->{PID}, 0);
delete $hash->{PID};
close($hash->{FH}) if($hash->{FH});
delete($hash->{FH});
delete($hash->{FD});
delete($main::selectlist{$name});
closeLogfile($hash) if( $hash->{log} );
$hash->{PARTIAL} = "" if( defined($hash->{PARTIAL}) );
main::Log3 $name, 3, "$name: stopped";
$hash->{LAST_STOP} = main::FmtDateTime( time() );
$hash->{CoProcess}{state} = 'stopped';
if( $hash->{reason} ) {
$hash->{CoProcess}{state} .= "; $hash->{reason}";
delete $hash->{reason};
}
main::readingsSingleUpdate($hash, $hash->{CoProcess}{name}, $hash->{CoProcess}{state}, 1 ) if( $hash->{CoProcess}{name} );
if( $hash->{undefine} ) {
my $cl = $hash->{undefine};
delete $hash->{undefine};
main::CommandDelete(undef, $name);
main::Log3 $name, 2, "$name: deleted";
main::asyncOutput( $cl, "$name: deleted\n" ) if( ref($cl) eq 'HASH' && $cl->{canAsyncOutput} );
} elsif( $hash->{shutdown} ) {
my $cl = $hash->{shutdown};
delete $hash->{shutdown};
main::asyncOutput( $cl, "$name: stopped\n" ) if( ref($cl) eq 'HASH' && $cl->{canAsyncOutput} );
main::CancelDelayedShutdown($name);
} elsif( $hash->{restart} ) {
start($hash)
}
}
#read from co process and handle logging
sub
readFn($) {
my ($hash) = @_;
my $name = $hash->{NAME};
my $buf;
my $ret = sysread($hash->{FH}, $buf, 65536 );
if(!defined($ret) || $ret <= 0) {
main::Log3 $name, 3, "$name: read: error during sysread: $!" if(!defined($ret));
main::Log3 $name, 3, "$name: read: end of file reached while sysread" if( $ret <= 0);
my $oldstate = $hash->{CoProcess}{state};
terminate($hash);
return undef if( $oldstate !~ m/^running/ );
my $delay = 20;
if( $hash->{'LAST_START'} && $hash->{'LAST_STOP'} ) {
my $diff = main::time_str2num($hash->{'LAST_STOP'}) - main::time_str2num($hash->{'LAST_START'});
if( $diff > 60 ) {
$delay = 0;
main::Log3 $name, 4, "$name: last run duration was $diff sec, restarting imediately";
} else {
main::Log3 $name, 4, "$name: last run duration was only $diff sec, restarting with delay";
}
} else {
main::Log3 $name, 4, "$name: last run duration unknown, restarting with delay";
}
main::InternalTimer(time()+$delay, "CoProcess::start", $hash, 0);
return undef;
}
if( $hash->{logfile} ) {
if( $hash->{log} ) {
my @t = localtime(time());
my $logfile = main::ResolveDateWildcards($hash->{logfile}, @t);
openLogfile($hash, $logfile) if( $hash->{currentlogfile} ne $logfile );
}
if( $hash->{log} ) {
print {$hash->{log}} "$buf";
} else {
#my $buf = $buf;
$buf =~ s/\n$//s;
main::Log3 $name, 3, "$name: $buf";
}
}
if( my $disabled = main::IsDisabled($hash) && !$hash->{CoProcess}{state} eq 'stopping' ) {
$hash->{reason} = 'disabledForIntervals';
CoProcess::stop($hash);
if( $disabled == 2 ) {
$hash->{disabled} = 1;
#FIXME: add timer to restart coprocess if disabledForIntervals has elapsed
}
}
return $buf;
}
# add CoProcess specific commands
sub
setCommands($$@) {
my ($hash, $list, $cmd, @a) = @_;
#my %cp_list = (
# 'start' => ':noArg',
# 'stop' => ':noArg',
# 'restart' => ':noArg',
#);
if( $cmd eq 'start' ) {
start($hash);
return undef;
} elsif( $cmd eq 'stop' ) {
stop($hash);
return undef;
} elsif( $cmd eq 'restart' ) {
start($hash);
return undef;
}
$list .= ' ' if( $list );
$list .= 'start:noArg stop:noArg restart:noArg';
#foreach my $key ( sort keys %cp_list ) {
# if( $list !~ m/\b$key\b/ ) {
# $list .= ' ' if( $list );
# $list .= $key.$cp_list{$key};
# }
#}
return "Unknown argument $cmd, choose one of $list";
}
# add CoProcess specific attributes
use vars qw($CoProcessAttributes);
#no warnings 'qw';
my @attrList = qw(
CoProc-cmd
CoProc-log
CoProc-params
CoProc-sshHost
CoProc-sshUser
);
$CoProcessAttributes = join(" ", @attrList);
#TODO...

View File

@ -152,8 +152,8 @@ FHEM/24_TPLinkHS110.pm VolkerKettenbach Sonstige Systeme
FHEM/26_tahoma.pm mike3436 Sonstige Systeme FHEM/26_tahoma.pm mike3436 Sonstige Systeme
FHEM/30_DUOFERN telekatz Sonstige Systeme FHEM/30_DUOFERN telekatz Sonstige Systeme
FHEM/30_ENECSYSGW.pm akw Sonstige Systeme FHEM/30_ENECSYSGW.pm akw Sonstige Systeme
FHEM/30_HUEBridge.pm justme1968 Beleuchtung FHEM/30_HUEBridge.pm justme1968 Zigbee
FHEM/30_LIGHTIFY.pm justme1968 Beleuchtung FHEM/30_LIGHTIFY.pm justme1968 Zigbee
FHEM/30_MilightBridge.pm mattwire Beleuchtung FHEM/30_MilightBridge.pm mattwire Beleuchtung
FHEM/30_pilight_contact.pm risiko Sonstige Systeme FHEM/30_pilight_contact.pm risiko Sonstige Systeme
FHEM/30_pilight_dimmer.pm risiko Sonstige Systeme FHEM/30_pilight_dimmer.pm risiko Sonstige Systeme
@ -161,9 +161,10 @@ FHEM/30_pilight_raw.pm risiko Sonstige Systeme
FHEM/30_pilight_smoke.pm risiko Sonstige Systeme FHEM/30_pilight_smoke.pm risiko Sonstige Systeme
FHEM/30_pilight_switch.pm risiko Sonstige Systeme FHEM/30_pilight_switch.pm risiko Sonstige Systeme
FHEM/30_pilight_temp.pm risiko Sonstige Systeme FHEM/30_pilight_temp.pm risiko Sonstige Systeme
FHEM/30_tradfri.pm justme1968 Zigbee
FHEM/31_Aurora.pm justme1968 Beleuchtung FHEM/31_Aurora.pm justme1968 Beleuchtung
FHEM/31_ENECSYSINV.pm akw Sonstige Systeme FHEM/31_ENECSYSINV.pm akw Sonstige Systeme
FHEM/31_HUEDevice.pm justme1968 Beleuchtung FHEM/31_HUEDevice.pm justme1968 Zigbee
FHEM/31_LightScene.pm justme1968 Automatisierung FHEM/31_LightScene.pm justme1968 Automatisierung
FHEM/31_MilightDevice.pm mattwire Beleuchtung FHEM/31_MilightDevice.pm mattwire Beleuchtung
FHEM/31_Nello.pm neumann Sonstige Systeme FHEM/31_Nello.pm neumann Sonstige Systeme
@ -521,6 +522,8 @@ FHEM/Blocking.pm rudolfkoenig Automatisierung
FHEM/Color.pm justme1968 Sonstiges FHEM/Color.pm justme1968 Sonstiges
FHEM/DarkSkyAPI.pm CoolTux Unterstuetzende Dienste/Wettermodule FHEM/DarkSkyAPI.pm CoolTux Unterstuetzende Dienste/Wettermodule
FHEM/DevIo.pm rudolfkoenig Sonstiges FHEM/DevIo.pm rudolfkoenig Sonstiges
FHEM/CoProcess.pm justme1968 Sonstiges
FHEM/Color.pm justme1968 Sonstiges
FHEM/FritzBoxUtils.pm rudolfkoenig FRITZ!Box FHEM/FritzBoxUtils.pm rudolfkoenig FRITZ!Box
FHEM/GPUtils.pm ntruchsess FHEM Development FHEM/GPUtils.pm ntruchsess FHEM Development
FHEM/HMCCUConf.pm zap HomeMatic FHEM/HMCCUConf.pm zap HomeMatic