From effe1db3dc6166068b0ca500a999cb771f2a61bb Mon Sep 17 00:00:00 2001 From: justme-1968 Date: Tue, 23 Dec 2014 16:47:52 +0000 Subject: [PATCH] HUEbridge: added group handling added pollDevices attribute added httpUtils for nonblocking mode HUEDevice: added group handling added renaming of devices and groups in the bridge git-svn-id: https://svn.fhem.de/fhem/trunk@7314 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 5 + fhem/FHEM/30_HUEBridge.pm | 375 +++++++++++++++++++++++++++++++++----- fhem/FHEM/31_HUEDevice.pm | 85 +++++++-- 3 files changed, 411 insertions(+), 54 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 35af2f45a..60b1278ba 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: HUEbridge: added group handling + added pollDevices attribute + added httpUtils for nonblocking mode + - feature: HUEDevice: added group handling + added renaming of devices and groups in the bridge - added: 30_MilightBridge / 31_MilightDevice: Support Milight (Applamp/LimitlessLED/EasyBulb) LED lights/strips/controllers. - feature: HUEDevice: allow ct presets in webCmd diff --git a/fhem/FHEM/30_HUEBridge.pm b/fhem/FHEM/30_HUEBridge.pm index 3c2b9d40f..db45b6b67 100644 --- a/fhem/FHEM/30_HUEBridge.pm +++ b/fhem/FHEM/30_HUEBridge.pm @@ -11,9 +11,10 @@ use strict; use warnings; use POSIX; use JSON; -#use Try::Tiny; use Data::Dumper; +use HttpUtils; + my $HUEBridge_isFritzBox = undef; sub HUEBridge_isFritzBox() @@ -23,7 +24,6 @@ HUEBridge_isFritzBox() return $HUEBridge_isFritzBox; } - sub HUEBridge_Initialize($) { my ($hash) = @_; @@ -40,18 +40,18 @@ sub HUEBridge_Initialize($) $hash->{SetFn} = "HUEBridge_Set"; $hash->{GetFn} = "HUEBridge_Get"; $hash->{UndefFn} = "HUEBridge_Undefine"; - $hash->{AttrList}= "key disable:1"; + $hash->{AttrList}= "key disable:1 httpUtils:1,0 pollDevices:1"; } sub HUEBridge_Read($@) { - my ($hash,$name,$id,$obj)= @_; + my ($hash,$chash,$name,$id,$obj)= @_; if( $id =~ m/^G(\d.*)/ ) { - return HUEBridge_Call($hash, 'groups/' . $1, $obj); + return HUEBridge_Call($hash, $chash, 'groups/' . $1, $obj); } - return HUEBridge_Call($hash, 'lights/' . $id, $obj); + return HUEBridge_Call($hash, $chash, 'lights/' . $id, $obj); } sub @@ -86,7 +86,7 @@ HUEBridge_Define($$) } $interval= 300 unless defined($interval); - if( $interval < 60 ) { $interval = 60; } + if( $interval < 10 ) { $interval = 10; } $hash->{STATE} = 'Initialized'; @@ -95,6 +95,8 @@ HUEBridge_Define($$) $attr{$name}{"key"} = join "",map { unpack "H*", chr(rand(256)) } 1..16 unless defined( AttrVal($name, "key", undef) ); + $hash->{helper}{last_config_timestamp} = 0; + if( !defined($hash->{helper}{count}) ) { $modules{$hash->{TYPE}}{helper}{count} = 0 if( !defined($modules{$hash->{TYPE}}{helper}{count}) ); $hash->{helper}{count} = $modules{$hash->{TYPE}}{helper}{count}++; @@ -135,7 +137,7 @@ sub HUEBridge_OpenDev($) { my ($hash) = @_; - my $result = HUEBridge_Call($hash, 'config', undef); + my $result = HUEBridge_Call($hash, undef, 'config', undef); if( !defined($result) ) { return undef; } @@ -181,18 +183,21 @@ sub HUEBridge_Pair($) sub HUEBridge_Set($@) { - my ($hash, $name, $cmd) = @_; + my ($hash, $name, $cmd, $arg, @params) = @_; # usage check if($cmd eq 'statusRequest') { - RemoveInternalTimer($hash); + $hash->{LOCAL} = 1; + #RemoveInternalTimer($hash); HUEBridge_GetUpdate($hash); + delete $hash->{LOCAL}; return undef; + } elsif($cmd eq 'swupdate') { my $obj = { 'swupdate' => { 'updatestate' => 3, }, }; - my $result = HUEBridge_Call($hash, 'config', $obj); + my $result = HUEBridge_Call($hash, undef, 'config', $obj); if( !defined($result) || $result->{'error'} ) { return $result->{'error'}->{'description'}; @@ -201,11 +206,53 @@ HUEBridge_Set($@) $hash->{updatestate} = 3; $hash->{STATE} = "updating"; return "starting update"; + } elsif($cmd eq 'autocreate') { - HUEBridge_Autocreate($hash,1); + return HUEBridge_Autocreate($hash,1); + + } elsif($cmd eq 'creategroup') { + + my @lights = (); + for my $param (@params) { + $param = $defs{$param}{ID} if( defined $defs{$param} && $defs{$param}{TYPE} eq 'HUEDevice' ); + push( @lights, $param ); + } + + my $obj = { 'name' => $arg, + 'lights' => \@lights, + }; + + my $result = HUEBridge_Call($hash, undef, 'groups', $obj, 'POST'); + + if( $result->{success} ) { + HUEBridge_Autocreate($hash); + + my $code = $name ."-G". $result->{success}{id}; + return "created $modules{HUEDevice}{defptr}{$code}->{NAME}" if( defined($modules{HUEDevice}{defptr}{$code}) ); + } + + return $result->{error}{description} if( $result->{error} ); return undef; + + } elsif($cmd eq 'deletegroup') { + if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) { + $defs{$arg}{ID} =~ m/G(.*)/; + $arg = $1; + } + + my $code = $name ."-G". $arg; + if( defined($modules{HUEDevice}{defptr}{$code}) ) { + CommandDelete( undef, "$modules{HUEDevice}{defptr}{$code}{NAME}" ); + CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) ); + } + + my $result = HUEBridge_Call($hash, undef, "groups/$arg", undef, 'DELETE'); + return $result->{error}{description} if( $result->{error} ); + + return undef; + } else { - my $list = "autocreate:noArg statusRequest:noArg"; + my $list = "creategroup deletegroup autocreate:noArg statusRequest:noArg"; $list .= " swupdate:noArg" if( defined($hash->{updatestate}) && $hash->{updatestate} == 2 ); return "Unknown argument $cmd, choose one of $list"; } @@ -220,20 +267,30 @@ HUEBridge_Get($@) # usage check if($cmd eq 'devices') { - my $result = HUEBridge_Call($hash, 'lights', undef); + my $result = HUEBridge_Call($hash, undef, 'lights', undef); my $ret = ""; - foreach my $key ( sort keys %$result ) { - $ret .= $key .": ". $result->{$key}{name} ."\n"; + foreach my $key ( sort {$a<=>$b} keys %$result ) { + my $code = $name ."-". $key; + my $fhem_name =""; + $fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) ); + $ret .= sprintf( "%2i: %-25s %-15s %s\n", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type} ); } + $ret = sprintf( "%2s %-25s %-15s %s\n", "ID", "NAME", "FHEM", "TYPE" ) .$ret if( $ret ); return $ret; + } elsif($cmd eq 'groups') { - my $result = HUEBridge_Call($hash, 'groups', undef); - $result->{0} = { name => "Lightset 0", }; + my $result = HUEBridge_Call($hash, undef, 'groups', undef); + $result->{0} = { name => 'Lightset 0', type => 'LightGroup', lights => ["ALL"] }; my $ret = ""; - foreach my $key ( sort keys %$result ) { - $ret .= $key .": ". $result->{$key}{name} ."\n"; + foreach my $key ( sort {$a<=>$b} keys %$result ) { + my $code = $name ."-G". $key; + my $fhem_name =""; + $fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) ); + $ret .= sprintf( "%2i: %-15s %-15s %-15s %s\n", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type}, join( ",", @{$result->{$key}{lights}} ) ); } + $ret = sprintf( "%2s %-15s %-15s %-15s %s\n", "ID", "NAME", "FHEM", "TYPE", "LIGHTS" ) .$ret if( $ret ); return $ret; + } else { return "Unknown argument $cmd, choose one of devices:noArg groups:noArg"; } @@ -250,16 +307,49 @@ HUEBridge_GetUpdate($) InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEBridge_GetUpdate", $hash, 0); } - my $result = HUEBridge_Call($hash, 'config', undef); - #my $result = HUEBridge_Call($hash, undef, undef); + my $type; + my $result; + if( AttrVal($name,"pollDevices",0) ) { + my ($now) = gettimeofday(); + if( $hash->{LOCAL} || $now - $hash->{helper}{last_config_timestamp} > 300 ) { + $result = HUEBridge_Call($hash, $hash, undef, undef); + $hash->{helper}{last_config_timestamp} = $now; + } else { + $type = 'lights'; + $result = HUEBridge_Call($hash, $hash, 'lights', undef); + } + } else { + $type = 'config'; + $result = HUEBridge_Call($hash, $hash, 'config', undef); + } + + return undef if( !defined($result) ); + + HUEBridge_dispatch( {hash=>$hash,chash=>$hash,type=>$type}, undef, undef, $result ); + + #HUEBridge_Parse($hash, $result); + + return undef; +} + +sub +HUEBridge_Parse($$) +{ + my($hash,$result) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 4, "parse status message for $name"; + #Log3 $name, 5, Dumper $result; + #Log 3, Dumper $result; - #$result = $result->{config}; + $result = $result->{config} if( defined($result->{config}) ); + $hash->{name} = $result->{name}; $hash->{swversion} = $result->{swversion}; if( defined( $result->{swupdate} ) ) { my $txt = $result->{swupdate}->{text}; - readingsSingleUpdate($hash, "swupdate", $txt, defined($hash->{LOCAL} ? 0 : 1)) if( $txt && $txt ne ReadingsVal($name,"swupdate","") ); + readingsSingleUpdate($hash, "swupdate", $txt, 1) if( $txt && $txt ne ReadingsVal($name,"swupdate","") ); if( defined($hash->{updatestate}) ){ $hash->{STATE} = "update done" if( $result->{swupdate}->{updatestate} == 0 && $hash->{updatestate} >= 2 ); $hash->{STATE} = "update failed" if( $result->{swupdate}->{updatestate} == 2 && $hash->{updatestate} == 3 ); @@ -284,13 +374,14 @@ HUEBridge_Autocreate($;$) } } - my $result = HUEBridge_Call($hash, 'lights', undef); + my $autocreated = 0; + my $result = HUEBridge_Call($hash,undef, 'lights', undef); foreach my $key ( keys %$result ) { my $id= $key; my $code = $name ."-". $id; if( defined($modules{HUEDevice}{defptr}{$code}) ) { - Log3 $name, 4, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'"; + Log3 $name, 5, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'"; next; } @@ -307,17 +398,19 @@ HUEBridge_Autocreate($;$) $cmdret= CommandAttr(undef,"$devname alias ".$result->{$id}{name}); $cmdret= CommandAttr(undef,"$devname room HUEDevice"); $cmdret= CommandAttr(undef,"$devname IODev $name"); + + $autocreated++; } } - $result = HUEBridge_Call($hash, 'groups', undef); + $result = HUEBridge_Call($hash,undef, 'groups', undef); $result->{0} = { name => "Lightset 0", }; foreach my $key ( keys %$result ) { my $id= $key; my $code = $name ."-G". $id; if( defined($modules{HUEDevice}{defptr}{$code}) ) { - Log3 $name, 4, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'"; + Log3 $name, 5, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'"; next; } @@ -334,10 +427,14 @@ HUEBridge_Autocreate($;$) $cmdret= CommandAttr(undef,"$devname alias ".$result->{$id}{name}); $cmdret= CommandAttr(undef,"$devname room HUEDevice"); $cmdret= CommandAttr(undef,"$devname IODev $name"); + + $autocreated++; } } - return undef; + CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) ); + + return "created $autocreated devices"; } sub HUEBridge_ProcessResponse($$) @@ -378,49 +475,56 @@ sub HUEBridge_Register($) 'devicetype' => 'fhem', }; - return HUEBridge_Call($hash, undef, $obj); + return HUEBridge_Call($hash, undef, undef, $obj); } #Executes a JSON RPC sub -HUEBridge_Call($$$) +HUEBridge_Call($$$$;$) { - my ($hash,$path,$obj) = @_; + my ($hash,$chash,$path,$obj,$method) = @_; + my $name = $hash->{NAME}; - #Log3 $name, 3, "Sending: " . Dumper $obj; + #Log3 $hash->{NAME}, 5, "Sending: " . Dumper $obj; my $json = undef; $json = encode_json($obj) if $obj; - return HUEBridge_HTTP_Call($hash,$path,$json); + if( !defined($attr{$name}{httpUtils}) ) { + return HUEBridge_HTTP_Call($hash,$path,$json,$method); + } else { + return HUEBridge_HTTP_Call2($hash,$chash,$path,$json,$method); + } } #JSON RPC over HTTP -sub HUEBridge_HTTP_Call($$$) +sub +HUEBridge_HTTP_Call($$$;$) { - my ($hash,$path,$obj) = @_; + my ($hash,$path,$obj,$method) = @_; my $name = $hash->{NAME}; return undef if($attr{$name} && $attr{$name}{disable}); #return { state => {reachable => 0 } } if($attr{$name} && $attr{$name}{disable}); my $uri = "http://" . $hash->{Host} . "/api"; - my $method = 'GET'; if( defined($obj) ) { - $method = 'PUT'; + $method = 'PUT' if( !$method ); - if( $hash->{STATE} eq 'Pairing' ) { - $method = 'POST'; - } else { - $uri .= "/" . AttrVal($name, "key", ""); - } + if( $hash->{STATE} eq 'Pairing' ) { + $method = 'POST'; } else { $uri .= "/" . AttrVal($name, "key", ""); } + } else { + $uri .= "/" . AttrVal($name, "key", ""); + } + $method = 'GET' if( !$method ); if( defined $path) { $uri .= "/" . $path; } #Log3 $name, 3, "Url: " . $uri; + Log3 $name, 4, "using HUEBridge_HTTP_Request: $method ". ($path?$path:''); my $ret = HUEBridge_HTTP_Request(0,$uri,$method,undef,$obj,undef); #Log3 $name, 3, Dumper $ret; if( !defined($ret) ) { @@ -442,6 +546,186 @@ sub HUEBridge_HTTP_Call($$$) return HUEBridge_ProcessResponse($hash,from_json($ret)); } +sub +HUEBridge_HTTP_Call2($$$$;$) +{ + my ($hash,$chash,$path,$obj,$method) = @_; + my $name = $hash->{NAME}; + + return undef if($attr{$name} && $attr{$name}{disable}); + #return { state => {reachable => 0 } } if($attr{$name} && $attr{$name}{disable}); + + my $url = "http://" . $hash->{Host} . "/api"; + my $blocking = $attr{$name}{httpUtils} < 1; + $blocking = 1 if( !defined($chash) ); + if( defined($obj) ) { + $method = 'PUT' if( !$method ); + + if( $hash->{STATE} eq 'Pairing' ) { + $method = 'POST'; + $blocking = 1; + } else { + $url .= "/" . AttrVal($name, "key", ""); + } + } else { + $url .= "/" . AttrVal($name, "key", ""); + } + $method = 'GET' if( !$method ); + + if( defined $path) { + $url .= "/" . $path; + } + #Log3 $name, 3, "Url: " . $url; + +#Log 2, $path; + if( $blocking ) { + Log3 $name, 4, "using HttpUtils_BlockingGet: $method ". ($path?$path:''); + + my($err,$data) = HttpUtils_BlockingGet({ + url => $url, + timeout => 4, + method => $method, + noshutdown => 1, + header => "Content-Type: application/json", + data => $obj, + }); + + return HUEBridge_ProcessResponse($hash,from_json($data)); + + HUEBridge_dispatch( {hash=>$hash,chash=>$chash,type=>$path},$err,$data ); + } else { + Log3 $name, 4, "using HttpUtils_NonblockingGet: $method ". ($path?$path:''); + + my($err,$data) = HttpUtils_NonblockingGet({ + url => $url, + timeout => 10, + method => $method, + noshutdown => 1, + header => "Content-Type: application/json", + data => $obj, + hash => $hash, + chash => $chash, + type => $path, + callback => \&HUEBridge_dispatch, + }); + + return undef; + } +} + +sub +HUEBridge_dispatch($$$;$) +{ + my ($param, $err, $data,$json) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + + #Log3 $name, 5, "HUEBridge_dispatch"; + + if( $err ) { + Log3 $name, 2, "$name: http request failed: $err"; + } elsif( $data || $json ) { + if( $data && $data !~ m/^[\[{].*[\]}]$/ ) { + Log3 $name, 2, "$name: invalid json detected: $data"; + return undef; + } + + $json = from_json($data) if( !$json ); + my $type = $param->{type}; + + if( ref($json) eq 'ARRAY' ) + { + if( defined($json->[0]->{error})) + { + my $error = $json->[0]->{error}->{'description'}; + + $hash->{STATE} = $error; + + Log3 $name, 3, $error; + } + + #return ($json->[0]); + } + + if( $hash == $param->{chash} ) { + if( !defined($type) ) { + HUEBridge_Parse($hash,$json->{config}); + + if( defined($json->{groups}) ) { + my $groups = $json->{groups}; + foreach my $id ( keys %{$groups} ) { + my $code = $name ."-G". $id; + my $chash = $modules{HUEDevice}{defptr}{$code}; + + if( defined($chash) ) { + HUEDevice_Parse($chash,$groups->{$id}); + } else { + Log3 $name, 2, "$name: message for unknow group received: $code"; + } + } + } + + $type = 'lights'; + $json = $json->{lights}; + + } + + if( $type eq 'lights' ) { + my $lights = $json; + foreach my $id ( keys %{$lights} ) { + my $code = $name ."-". $id; + my $chash = $modules{HUEDevice}{defptr}{$code}; + + if( defined($chash) ) { + HUEDevice_Parse($chash,$lights->{$id}); + } else { + Log3 $name, 2, "$name: message for unknow device received: $code"; + } + } + + } elsif( $type =~ m/^config$/ ) { + HUEBridge_Parse($hash,$json); + + } else { + Log3 $name, 2, "$name: message for unknow type received: $type"; + Log3 $name, 4, Dumper $json; + + } + + } elsif( $type =~ m/^lights\/(\d*)$/ ) { + HUEDevice_Parse($param->{chash},$json); + + } elsif( $type =~ m/^groups\/(\d*)$/ ) { + HUEDevice_Parse($param->{chash},$json); + + } elsif( $type =~ m/^lights\/(\d*)\/state$/ ) { + my $chash = $param->{chash}; + if( $chash->{helper}->{update_timeout} ) { + RemoveInternalTimer($chash); + InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0); + } else { + RemoveInternalTimer($chash); + HUEDevice_GetUpdate( $chash ); + } + + } elsif( $type =~ m/^groups\/(\d*)\/action$/ ) { + my $chash = $param->{chash}; + if( $chash->{helper}->{update_timeout} ) { + RemoveInternalTimer($chash); + InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0); + } else { + RemoveInternalTimer($chash); + HUEDevice_GetUpdate( $chash ); + } + + } else { + Log3 $name, 2, "$name: message for unknow type received: $type"; + Log3 $name, 4, Dumper $json; + + } + } +} + #adapted version of the CustomGetFileFromURL subroutine from HttpUtils.pm sub HUEBridge_HTTP_Request($$$@) @@ -587,6 +871,11 @@ HUEBridge_HTTP_Request($$$@)