HUEbridge: added support for HUE scenes

(get: scenes; set: savescene, modifyscene, scene)
           new unified syntax for multiple lights
             in group and scene commands
           don't query status after set: use bridge response instead
           added queryAfterSet attribute
HUEDevice: added support for HUE scenes (set: savescene, scene)
           new unified syntax for multiple lights in lights command
LightScene: support HUE scenes
            added asyncDelay attribute


git-svn-id: svn://svn.code.sf.net/p/fhem/code/trunk@9451 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
justme1968
2015-10-13 18:25:37 +00:00
parent 85ce073347
commit 20f1c27db5
4 changed files with 312 additions and 74 deletions

View File

@@ -1,5 +1,15 @@
# 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.
- feature: HUEbridge: added support for HUE scenes
(get: scenes; set: savescene, modifyscene, scene)
new unified syntax for multiple lights
in group and scene commands
don't query status after set: use bridge response instead
added queryAfterSet attribute
- feature: HUEDevice: added support for HUE scenes (set: savescene, scene)
new unified syntax for multiple lights in lights command
- feature: LightScene: support HUE scenes
added asyncDelay attribute
- bugfix: 10_IT: Fix wrong V3 dimm/off code - bugfix: 10_IT: Fix wrong V3 dimm/off code
- feature: 73_km200: Improving error message in state - feature: 73_km200: Improving error message in state
- feature: SYSMON: FormatString for SYSMON_ShowValues. - feature: SYSMON: FormatString for SYSMON_ShowValues.

View File

@@ -39,7 +39,7 @@ sub HUEBridge_Initialize($)
$hash->{SetFn} = "HUEBridge_Set"; $hash->{SetFn} = "HUEBridge_Set";
$hash->{GetFn} = "HUEBridge_Get"; $hash->{GetFn} = "HUEBridge_Get";
$hash->{UndefFn} = "HUEBridge_Undefine"; $hash->{UndefFn} = "HUEBridge_Undefine";
$hash->{AttrList}= "key disable:1 httpUtils:1,0 pollDevices:1"; $hash->{AttrList}= "key disable:1 httpUtils:1,0 pollDevices:1 queryAfterSet:1";
} }
sub sub
@@ -226,14 +226,39 @@ sub HUEBridge_Pair($)
return undef; return undef;
} }
sub
HUEBridge_string2array($)
{
my ($lights) = @_;
my %lights = ();
foreach my $part ( split(',', $lights) ) {
my $light = $part;
$light = $defs{$light}{ID} if( defined $defs{$light} && $defs{$light}{TYPE} eq 'HUEDevice' );
if( $light =~ m/^G/ ) {
my $lights = $defs{$part}->{lights};
foreach my $light ( split(',', $lights) ) {
$lights{$light} = 1;
}
} else {
$lights{$light} = 1;
}
}
my @lights = sort {$a<=>$b} keys(%lights);
return \@lights;
}
sub sub
HUEBridge_Set($@) HUEBridge_Set($@)
{ {
my ($hash, $name, $cmd, $arg, @params) = @_; my ($hash, $name, $cmd, @args) = @_;
my ($arg, @params) = @args;
# usage check # usage check
if($cmd eq 'statusRequest') { if($cmd eq 'statusRequest') {
return "usage: statusRequest" if( @args != 0 );
$hash->{LOCAL} = 1; $hash->{LOCAL} = 1;
#RemoveInternalTimer($hash); #RemoveInternalTimer($hash);
HUEBridge_GetUpdate($hash); HUEBridge_GetUpdate($hash);
@@ -241,6 +266,8 @@ HUEBridge_Set($@)
return undef; return undef;
} elsif($cmd eq 'swupdate') { } elsif($cmd eq 'swupdate') {
return "usage: swupdate" if( @args != 0 );
my $obj = { my $obj = {
'swupdate' => { 'updatestate' => 3, }, 'swupdate' => { 'updatestate' => 3, },
}; };
@@ -256,9 +283,13 @@ HUEBridge_Set($@)
return "starting update"; return "starting update";
} elsif($cmd eq 'autocreate') { } elsif($cmd eq 'autocreate') {
return "usage: autocreate" if( @args != 0 );
return HUEBridge_Autocreate($hash,1); return HUEBridge_Autocreate($hash,1);
} elsif($cmd eq 'autodetect') { } elsif($cmd eq 'autodetect') {
return "usage: autodetect" if( @args != 0 );
my $result = HUEBridge_Call($hash, undef, 'lights', undef, 'POST'); my $result = HUEBridge_Call($hash, undef, 'lights', undef, 'POST');
return $result->{success}{'/lights'} if( $result->{success} ); return $result->{success}{'/lights'} if( $result->{success} );
@@ -267,10 +298,14 @@ HUEBridge_Set($@)
return undef; return undef;
} elsif($cmd eq 'delete') { } elsif($cmd eq 'delete') {
return "usage: delete <id>" if( @args != 1 );
if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) { if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) {
$arg = $defs{$arg}{ID}; $arg = $defs{$arg}{ID};
} }
return "$arg is not hue light number" if( $arg !~ m/^\d+$/ );
my $code = $name ."-". $arg; my $code = $name ."-". $arg;
if( defined($modules{HUEDevice}{defptr}{$code}) ) { if( defined($modules{HUEDevice}{defptr}{$code}) ) {
CommandDelete( undef, "$modules{HUEDevice}{defptr}{$code}{NAME}" ); CommandDelete( undef, "$modules{HUEDevice}{defptr}{$code}{NAME}" );
@@ -283,15 +318,10 @@ HUEBridge_Set($@)
return undef; return undef;
} elsif($cmd eq 'creategroup') { } elsif($cmd eq 'creategroup') {
return "usage: creategroup <name> <lights>" if( @args < 2 );
my @lights = (); my $obj = { 'name' => join( ' ', @args[0..@args-2]),
for my $param (@params) { 'lights' => HUEBridge_string2array($args[@args-1]),
$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'); my $result = HUEBridge_Call($hash, undef, 'groups', $obj, 'POST');
@@ -307,7 +337,10 @@ HUEBridge_Set($@)
return undef; return undef;
} elsif($cmd eq 'deletegroup') { } elsif($cmd eq 'deletegroup') {
return "usage: deletegroup <id>" if( @args != 1 );
if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) { if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) {
return "$arg is not a hue group" if( $defs{$arg}{ID} != m/^G/ );
$defs{$arg}{ID} =~ m/G(.*)/; $defs{$arg}{ID} =~ m/G(.*)/;
$arg = $1; $arg = $1;
} }
@@ -318,12 +351,66 @@ HUEBridge_Set($@)
CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) ); CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
} }
return "$arg is not hue group number" if( $arg !~ m/^\d+$/ );
my $result = HUEBridge_Call($hash, undef, "groups/$arg", undef, 'DELETE'); my $result = HUEBridge_Call($hash, undef, "groups/$arg", undef, 'DELETE');
return $result->{error}{description} if( $result->{error} ); return $result->{error}{description} if( $result->{error} );
return undef; return undef;
} elsif($cmd eq 'savescene') {
return "usage: savescene <id> <name> <lights>" if( @args < 3 );
my $obj = { 'name' => join( ' ', @args[1..@args-2]),
'lights' => HUEBridge_string2array($args[@args-1]),
};
my $result = HUEBridge_Call($hash, undef, "scenes/$arg", $obj, 'PUT');
if( $result->{success} ) {
return "created $arg";
}
return $result->{error}{description} if( $result->{error} );
return undef;
} elsif($cmd eq 'modifyscene') {
return "usage: modifyscene <id> <light> <light args>" if( @args < 3 );
my( $light, @aa ) = @params;
$light = $defs{$light}{ID} if( defined $defs{$light} && $defs{$light}{TYPE} eq 'HUEDevice' );
my %obj;
if( (my $joined = join(" ", @aa)) =~ /:/ ) {
my @cmds = split(":", $joined);
for( my $i = 0; $i <= $#cmds; ++$i ) {
HUEDevice_SetParam(undef, \%obj, split(" ", $cmds[$i]) );
}
} else {
my ($cmd, $value, $value2, @a) = @aa;
HUEDevice_SetParam(undef, \%obj, $cmd, $value, $value2);
}
my $result = HUEBridge_Call($hash, undef, "scenes/$arg/lights/$light/state", \%obj, 'PUT');
return $result->{error}{description} if( $result->{error} );
return undef;
} elsif($cmd eq 'scene') {
return "usage: scene <id>" if( @args != 1 );
my $obj = { 'scene' => $arg };
my $result = HUEBridge_Call($hash, undef, "groups/0/action", $obj, 'PUT');
return $result->{error}{description} if( $result->{error} );
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+10, "HUEBridge_GetUpdate", $hash, 0);
return undef;
} elsif($cmd eq 'deletewhitelist') { } elsif($cmd eq 'deletewhitelist') {
return "usage: deletewhitelist <key>" if( @args != 1 );
my $result = HUEBridge_Call($hash, undef, "config/whitelist/$arg", undef, 'DELETE'); my $result = HUEBridge_Call($hash, undef, "config/whitelist/$arg", undef, 'DELETE');
return $result->{error}{description} if( $result->{error} ); return $result->{error}{description} if( $result->{error} );
@@ -331,6 +418,8 @@ HUEBridge_Set($@)
return undef; return undef;
} elsif($cmd eq 'touchlink') { } elsif($cmd eq 'touchlink') {
return "usage: touchlink" if( @args != 0 );
my $obj = { 'touchlink' => JSON::true }; my $obj = { 'touchlink' => JSON::true };
my $result = HUEBridge_Call($hash, undef, 'config', $obj, 'PUT'); my $result = HUEBridge_Call($hash, undef, 'config', $obj, 'PUT');
@@ -342,7 +431,7 @@ HUEBridge_Set($@)
} else { } else {
my $list = "delete creategroup deletegroup deletewhitlist touchlink autocreate:noArg statusRequest:noArg"; my $list = "delete creategroup deletegroup savescene modifyscene scene deletewhitlist touchlink autocreate:noArg statusRequest:noArg";
$list .= " swupdate:noArg" if( defined($hash->{updatestate}) && $hash->{updatestate} =~ '^2' ); $list .= " swupdate:noArg" if( defined($hash->{updatestate}) && $hash->{updatestate} =~ '^2' );
return "Unknown argument $cmd, choose one of $list"; return "Unknown argument $cmd, choose one of $list";
} }
@@ -356,10 +445,11 @@ HUEBridge_Get($@)
return "$name: get needs at least one parameter" if( !defined($cmd) ); return "$name: get needs at least one parameter" if( !defined($cmd) );
# usage check # usage check
if($cmd eq 'devices') { if($cmd eq 'devices'
|| $cmd eq 'lights') {
my $result = HUEBridge_Call($hash, undef, 'lights', undef); my $result = HUEBridge_Call($hash, undef, 'lights', undef);
my $ret = ""; my $ret = "";
foreach my $key ( sort {$a<=>$b} keys %$result ) { foreach my $key ( sort {$a<=>$b} keys %{$result} ) {
my $code = $name ."-". $key; my $code = $name ."-". $key;
my $fhem_name =""; my $fhem_name ="";
$fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) ); $fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) );
@@ -372,7 +462,7 @@ HUEBridge_Get($@)
my $result = HUEBridge_Call($hash, undef, 'groups', undef); my $result = HUEBridge_Call($hash, undef, 'groups', undef);
$result->{0} = { name => 'Lightset 0', type => 'LightGroup', lights => ["ALL"] }; $result->{0} = { name => 'Lightset 0', type => 'LightGroup', lights => ["ALL"] };
my $ret = ""; my $ret = "";
foreach my $key ( sort {$a<=>$b} keys %$result ) { foreach my $key ( sort {$a<=>$b} keys %{$result} ) {
my $code = $name ."-G". $key; my $code = $name ."-G". $key;
my $fhem_name =""; my $fhem_name ="";
$fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) ); $fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) );
@@ -381,16 +471,25 @@ HUEBridge_Get($@)
$ret = sprintf( "%2s %-15s %-15s %-15s %s\n", "ID", "NAME", "FHEM", "TYPE", "LIGHTS" ) .$ret if( $ret ); $ret = sprintf( "%2s %-15s %-15s %-15s %s\n", "ID", "NAME", "FHEM", "TYPE", "LIGHTS" ) .$ret if( $ret );
return $ret; return $ret;
} elsif($cmd eq 'scenes') {
my $result = HUEBridge_Call($hash, undef, 'scenes', undef);
my $ret = "";
foreach my $key ( sort {$a cmp $b} keys %{$result} ) {
$ret .= sprintf( "%-20s %-20s %s\n", $key, $result->{$key}{name}, join( ",", @{$result->{$key}{lights}} ) );
}
$ret = sprintf( "%-20s %-20s %s\n", "ID", "NAME", "LIGHTS" ) .$ret if( $ret );
return $ret;
} elsif($cmd eq 'sensors') { } elsif($cmd eq 'sensors') {
my $result = HUEBridge_Call($hash, undef, 'sensors', undef); my $result = HUEBridge_Call($hash, undef, 'sensors', undef);
my $ret = ""; my $ret = "";
foreach my $key ( sort {$a<=>$b} keys %$result ) { foreach my $key ( sort {$a<=>$b} keys %{$result} ) {
my $code = $name ."-S". $key; my $code = $name ."-S". $key;
my $fhem_name =""; my $fhem_name ="";
$fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) ); $fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) );
$ret .= sprintf( "%2i: %-15s %-15s %-15s\n", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type} ); $ret .= sprintf( "%2i: %-15s %-15s %-15s\n", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type} );
} }
$ret = sprintf( "%2s %-15s %-15s %-15s %s\n", "ID", "NAME", "FHEM", "TYPE", "LIGHTS" ) .$ret if( $ret ); $ret = sprintf( "%2s %-15s %-15s %-15s\n", "ID", "NAME", "FHEM", "TYPE" ) .$ret if( $ret );
return $ret; return $ret;
} elsif($cmd eq 'whitelist') { } elsif($cmd eq 'whitelist') {
@@ -404,7 +503,7 @@ HUEBridge_Get($@)
return $ret; return $ret;
} else { } else {
return "Unknown argument $cmd, choose one of devices:noArg groups:noArg sensors:noArg whitelist:noArg"; return "Unknown argument $cmd, choose one of devices:noArg groups:noArg scenes:noArg sensors:noArg whitelist:noArg";
} }
} }
@@ -502,7 +601,7 @@ HUEBridge_Autocreate($;$)
my $autocreated = 0; my $autocreated = 0;
my $result = HUEBridge_Call($hash,undef, 'lights', undef); my $result = HUEBridge_Call($hash,undef, 'lights', undef);
foreach my $key ( keys %$result ) { foreach my $key ( keys %{$result} ) {
my $id= $key; my $id= $key;
my $code = $name ."-". $id; my $code = $name ."-". $id;
@@ -531,7 +630,7 @@ HUEBridge_Autocreate($;$)
$result = HUEBridge_Call($hash,undef, 'groups', undef); $result = HUEBridge_Call($hash,undef, 'groups', undef);
$result->{0} = { name => "Lightset 0", }; $result->{0} = { name => "Lightset 0", };
foreach my $key ( keys %$result ) { foreach my $key ( keys %{$result} ) {
my $id= $key; my $id= $key;
my $code = $name ."-G". $id; my $code = $name ."-G". $id;
@@ -580,23 +679,38 @@ sub HUEBridge_ProcessResponse($$)
$hash->{STATE} = $error; $hash->{STATE} = $error;
} }
if( 0 ) { if( !AttrVal( $name,'queryAfterSet', 0 ) ) {
my $successes;
my $errors;
my %json = (); my %json = ();
foreach my $item (@{$obj}) { foreach my $item (@{$obj}) {
if( my $success = $item->{success} ) { if( my $success = $item->{success} ) {
next if( ref($success) ne 'HASH' );
foreach my $key ( keys %{$success} ) { foreach my $key ( keys %{$success} ) {
my @l = split( '/', $key ); my @l = split( '/', $key );
next if( !$l[1] );
if( $l[1] eq 'lights' && $l[3] eq 'state' ) { if( $l[1] eq 'lights' && $l[3] eq 'state' ) {
$json{$l[2]}->{state}->{$l[4]} = $success->{$key}; $json{$l[2]}->{state}->{$l[4]} = $success->{$key};
$successes++;
} elsif( $l[1] eq 'groups' && $l[3] eq 'action' ) {
my $code = $name ."-G". $l[2];
my $d = $modules{HUEDevice}{defptr}{$code};
my $lights = $d->{lights};
foreach my $light ( split(',', $lights) ) {
$json{$light}->{state}->{$l[4]} = $success->{$key};
$successes++;
}
} }
} }
} elsif( my $error = $item->{error} ) { } elsif( my $error = $item->{error} ) {
my $msg = $error->{'description'}; my $msg = $error->{'description'};
Log3 $name, 3, $msg; Log3 $name, 3, $msg;
$errors++;
} }
} }
#Log 3, Dumper \%json;
foreach my $id ( keys %json ) { foreach my $id ( keys %json ) {
my $code = $name ."-". $id; my $code = $name ."-". $id;
@@ -605,7 +719,9 @@ if( 0 ) {
HUEDevice_Parse( $chash, $json{$id} ); HUEDevice_Parse( $chash, $json{$id} );
} }
} }
} }
#return undef if( !$errors && $successes );
return ($obj->[0]); return ($obj->[0]);
} }
@@ -698,7 +814,8 @@ HUEBridge_HTTP_Call($$$;$)
} elsif($ret eq '') { } elsif($ret eq '') {
return undef; return undef;
} elsif($ret =~ /^error:(\d){3}$/) { } elsif($ret =~ /^error:(\d){3}$/) {
return "HTTP Error Code " . $1; my %result = { error => "HTTP Error Code $1" };
return \%result;
} }
if( !$ret ) { if( !$ret ) {
@@ -807,11 +924,15 @@ HUEBridge_dispatch($$$;$)
return undef; return undef;
} }
my $queryAfterSet = AttrVal( $name,'queryAfterSet', 0 );
$json = from_json($data) if( !$json ); $json = from_json($data) if( !$json );
my $type = $param->{type}; my $type = $param->{type};
if( ref($json) eq 'ARRAY' ) if( ref($json) eq 'ARRAY' )
{ {
HUEBridge_ProcessResponse($hash,$json) if( !$queryAfterSet );
if( defined($json->[0]->{error})) if( defined($json->[0]->{error}))
{ {
my $error = $json->[0]->{error}->{'description'}; my $error = $json->[0]->{error}->{'description'};
@@ -892,13 +1013,15 @@ HUEBridge_dispatch($$$;$)
HUEDevice_Parse($param->{chash},$json); HUEDevice_Parse($param->{chash},$json);
} elsif( $type =~ m/^lights\/(\d*)\/state$/ ) { } elsif( $type =~ m/^lights\/(\d*)\/state$/ ) {
my $chash = $param->{chash}; if( $queryAfterSet ) {
if( $chash->{helper}->{update_timeout} ) { my $chash = $param->{chash};
RemoveInternalTimer($chash); if( $chash->{helper}->{update_timeout} ) {
InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0); RemoveInternalTimer($chash);
} else { InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0);
RemoveInternalTimer($chash); } else {
HUEDevice_GetUpdate( $chash ); RemoveInternalTimer($chash);
HUEDevice_GetUpdate( $chash );
}
} }
} elsif( $type =~ m/^groups\/(\d*)\/action$/ ) { } elsif( $type =~ m/^groups\/(\d*)\/action$/ ) {
@@ -1000,7 +1123,8 @@ HUEBridge_HTTP_Request($$$@)
} }
undef $conn; undef $conn;
if($header[0] =~ /^[^ ]+ ([\d]{3})/ && $1 != 200) { if($header[0] =~ /^[^ ]+ ([\d]{3})/ && $1 != 200) {
return "error:" . $1; my %result = { error => "error: $1" };
return \%result;
} }
return $ret; return $ret;
} }
@@ -1057,6 +1181,8 @@ HUEBridge_HTTP_Request($$$@)
list the devices known to the bridge.</li> list the devices known to the bridge.</li>
<li>groups<br> <li>groups<br>
list the groups known to the bridge.</li> list the groups known to the bridge.</li>
<li>scenes<br>
list the scenes known to the bridge.</li>
<li>sensors<br> <li>sensors<br>
list the sensors known to the bridge.</li> list the sensors known to the bridge.</li>
<li>whitelist<br> <li>whitelist<br>
@@ -1074,11 +1200,18 @@ HUEBridge_HTTP_Request($$$@)
can be created by <code>set <bridge> autocreate</code>.</li> can be created by <code>set <bridge> autocreate</code>.</li>
<li>delete &lt;name&gt;|&lt;id&gt;<br> <li>delete &lt;name&gt;|&lt;id&gt;<br>
Deletes the given device in the bridge and deletes the associated fhem device.</li> Deletes the given device in the bridge and deletes the associated fhem device.</li>
<li>creategroup &lt;name&gt; &lt;light-1&gt[ &lt;light-2&gt;..&lt;lignt-n&gt;]<br> <li>creategroup &lt;name&gt; &lt;lights&gt;<br>
Create a group out of &lt;light-1&gt-&lt;light-n&gt in the bridge. Create a group out of &lt;lights&gt; in the bridge.
The lights can be given as fhem device names or bridge device numbers.</li> The lights are given as a comma sparated list of fhem device names or bridge light numbers.</li>
<li>deletegroup &lt;name&gt;|&lt;id&gt;<br> <li>deletegroup &lt;name&gt;|&lt;id&gt;<br>
Deletes the given group in the bridge and deletes the associated fhem device.</li> Deletes the given group in the bridge and deletes the associated fhem device.</li>
<li>savescene &lt;id&gt; &lt;name&gt; &lt;lights&gt;<br>
Create a scene from the current state of &lt;lights&gt; in the bridge.
The lights are given as a comma sparated list of fhem device names or bridge light numbers.</li>
<li>scene &lt;id&gt;<br>
Recalls the scene with the given id.</li>
<li>modifyscene &lt;id&gt; &lt;light&gt; &lt;light-args&gt;<br>
Modifys the given scene in the bridge.</li>
<li>deletwhitelist &lt;key&gt;<br> <li>deletwhitelist &lt;key&gt;<br>
Deletes the given key from the whitelist in the bridge.</li> Deletes the given key from the whitelist in the bridge.</li>
<li>touchlink<br> <li>touchlink<br>

View File

@@ -16,6 +16,9 @@ use POSIX;
use JSON; use JSON;
use SetExtensions; use SetExtensions;
#require "30_HUEBridge.pm";
#require "$attr{global}{modpath}/FHEM/30_HUEBridge.pm";
use vars qw(%FW_webArgs); # all arguments specified in the GET use vars qw(%FW_webArgs); # all arguments specified in the GET
my %hueModels = ( my %hueModels = (
@@ -270,7 +273,7 @@ HUEDevice_SetParam($$@)
if( $cmd eq "color" ) { if( $cmd eq "color" ) {
$value = int(1000000/$value); $value = int(1000000/$value);
$cmd = 'ct'; $cmd = 'ct';
} elsif( $cmd eq "toggle" ) { } elsif( $name && $cmd eq "toggle" ) {
$cmd = ReadingsVal($name,"state","on") eq "off" ? "on" :"off"; $cmd = ReadingsVal($name,"state","on") eq "off" ? "on" :"off";
} elsif( $cmd =~ m/^dim(\d+)/ ) { } elsif( $cmd =~ m/^dim(\d+)/ ) {
$value2 = $value; $value2 = $value;
@@ -290,7 +293,7 @@ HUEDevice_SetParam($$@)
if($cmd eq 'on') { if($cmd eq 'on') {
$obj->{'on'} = JSON::true; $obj->{'on'} = JSON::true;
$obj->{'bri'} = 254 if( ReadingsVal($name,"bri","0") eq 0 ); $obj->{'bri'} = 254 if( $name && ReadingsVal($name,"bri","0") eq 0 );
$obj->{'transitiontime'} = $value * 10 if( defined($value) ); $obj->{'transitiontime'} = $value * 10 if( defined($value) );
} elsif($cmd eq 'off') { } elsif($cmd eq 'off') {
@@ -318,13 +321,13 @@ HUEDevice_SetParam($$@)
$obj->{'bri'} = 0+$value; $obj->{'bri'} = 0+$value;
$obj->{'transitiontime'} = $value2 * 10 if( defined($value2) ); $obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
} elsif($cmd eq "dimUp") { } elsif($name && $cmd eq "dimUp") {
if( $defs{$name}->{IODev}->{helper}{apiversion} && $defs{$name}->{IODev}->{helper}{apiversion} >= (1<<16) + (7<<8) ) { if( $defs{$name}->{IODev}->{helper}{apiversion} && $defs{$name}->{IODev}->{helper}{apiversion} >= (1<<16) + (7<<8) ) {
$obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} ); $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
$obj->{'bri_inc'} = 25; $obj->{'bri_inc'} = 25;
$obj->{'bri_inc'} = $value if( defined($value) ); $obj->{'bri_inc'} = $value if( defined($value) );
$obj->{'transitiontime'} = 1; $obj->{'transitiontime'} = 1;
$defs{$name}->{helper}->{update_timeout} = 0; #$defs{$name}->{helper}->{update_timeout} = 0;
} else { } else {
my $bri = ReadingsVal($name,"bri","0"); my $bri = ReadingsVal($name,"bri","0");
$bri += 25; $bri += 25;
@@ -336,13 +339,13 @@ HUEDevice_SetParam($$@)
$defs{$name}->{helper}->{update_timeout} = 0; $defs{$name}->{helper}->{update_timeout} = 0;
} }
} elsif($cmd eq "dimDown") { } elsif($name && $cmd eq "dimDown") {
if( $defs{$name}->{IODev}->{helper}{apiversion} && $defs{$name}->{IODev}->{helper}{apiversion} >= (1<<16) + (7<<8) ) { if( $defs{$name}->{IODev}->{helper}{apiversion} && $defs{$name}->{IODev}->{helper}{apiversion} >= (1<<16) + (7<<8) ) {
$obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} ); $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
$obj->{'bri_inc'} = -25; $obj->{'bri_inc'} = -25;
$obj->{'bri_inc'} = -$value if( defined($value) ); $obj->{'bri_inc'} = -$value if( defined($value) );
$obj->{'transitiontime'} = 1; $obj->{'transitiontime'} = 1;
$defs{$name}->{helper}->{update_timeout} = 0; #$defs{$name}->{helper}->{update_timeout} = 0;
} else { } else {
my $bri = ReadingsVal($name,"bri","0"); my $bri = ReadingsVal($name,"bri","0");
$bri -= 25; $bri -= 25;
@@ -375,7 +378,7 @@ HUEDevice_SetParam($$@)
} elsif( $cmd eq "rgb" && $value =~ m/^(..)(..)(..)/) { } elsif( $cmd eq "rgb" && $value =~ m/^(..)(..)(..)/) {
my( $r, $g, $b ) = (hex($1)/255.0, hex($2)/255.0, hex($3)/255.0); my( $r, $g, $b ) = (hex($1)/255.0, hex($2)/255.0, hex($3)/255.0);
if( !defined( AttrVal($name, "model", undef) ) ) { if( $name && !defined( AttrVal($name, "model", undef) ) ) {
my( $h, $s, $v ) = Color::rgb2hsv($r,$g,$b); my( $h, $s, $v ) = Color::rgb2hsv($r,$g,$b);
$obj->{'on'} = JSON::true; $obj->{'on'} = JSON::true;
@@ -426,11 +429,11 @@ HUEDevice_SetParam($$@)
$obj->{'effect'} = $value; $obj->{'effect'} = $value;
} elsif( $cmd eq "transitiontime" ) { } elsif( $cmd eq "transitiontime" ) {
$obj->{'transitiontime'} = 0+$value; $obj->{'transitiontime'} = 0+$value;
} elsif( $cmd eq "delayedUpdate" ) { } elsif( $name && $cmd eq "delayedUpdate" ) {
$defs{$name}->{helper}->{update_timeout} = 1; $defs{$name}->{helper}->{update_timeout} = 1;
} elsif( $cmd eq "immediateUpdate" ) { } elsif( $name && $cmd eq "immediateUpdate" ) {
$defs{$name}->{helper}->{update_timeout} = 0; $defs{$name}->{helper}->{update_timeout} = 0;
} elsif( $cmd eq "noUpdate" ) { } elsif( $name && $cmd eq "noUpdate" ) {
$defs{$name}->{helper}->{update_timeout} = -1; $defs{$name}->{helper}->{update_timeout} = -1;
} else { } else {
return 0; return 0;
@@ -443,20 +446,17 @@ sub
HUEDevice_Set($@) HUEDevice_Set($@)
{ {
my ($hash, $name, @aa) = @_; my ($hash, $name, @aa) = @_;
my ($cmd, @args) = @aa;
my %obj; my %obj;
$hash->{helper}->{update_timeout} = AttrVal($name, "delayedUpdate", 0); $hash->{helper}->{update_timeout} = AttrVal($name, "delayedUpdate", 1);
if( $hash->{helper}->{devtype} eq 'G' ) { if( $hash->{helper}->{devtype} eq 'G' ) {
if( $aa[0] eq 'lights' ) { if( $cmd eq 'lights' ) {
my @lights = (); return "usage: lights <lights>" if( @args != 1 );
for my $param (@aa[1..@aa-1]) {
$param = $defs{$param}{ID} if( defined $defs{$param} && $defs{$param}{TYPE} eq 'HUEDevice' );
push( @lights, $param );
}
my $obj = { 'lights' => \@lights, }; my $obj = { 'lights' => HUEBridge_string2array($args[0]), };
my $result = HUEDevice_ReadFromServer($hash,$hash->{ID},$obj); my $result = HUEDevice_ReadFromServer($hash,$hash->{ID},$obj);
if( $result->{success} ) { if( $result->{success} ) {
@@ -465,20 +465,51 @@ HUEDevice_Set($@)
} }
return $result->{error}{description} if( $result->{error} ); return $result->{error}{description} if( $result->{error} );
return undef;
} elsif( $cmd eq 'savescene' ) {
return "usage: savescene <id>" if( @args != 1 );
return fhem( "set $hash->{IODev}{NAME} savescene $aa[1] $aa[1] $hash->{NAME}" );
} elsif( $cmd eq 'scene' ) {
return "usage: scene <id>" if( @args != 1 );
my $obj = { 'scene' => $aa[1] };
$hash->{helper}->{update} = 1;
my $result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/action",$obj);
return $result->{error}{description} if( $result->{error} );
if( defined($result) && $result->{'error'} ) {
$hash->{STATE} = $result->{'error'}->{'description'};
return undef;
}
return undef if( !defined($result) );
if( $hash->{helper}->{update_timeout} == -1 ) {
} elsif( $hash->{helper}->{update_timeout} ) {
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{helper}->{update_timeout}, "HUEDevice_GetUpdate", $hash, 0);
} else {
RemoveInternalTimer($hash);
HUEDevice_GetUpdate( $hash );
}
return undef;
} }
} elsif( $hash->{helper}->{devtype} eq 'S' ) { } elsif( $hash->{helper}->{devtype} eq 'S' ) {
if( $aa[0] eq "statusRequest" ) { if( $cmd eq "statusRequest" ) {
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
HUEDevice_GetUpdate($hash); HUEDevice_GetUpdate($hash);
return undef; return undef;
} }
return "Unknown argument $aa[0], choose one of statusRequest:noArg"; return "Unknown argument $cmd, choose one of statusRequest:noArg";
} }
if( $aa[0] eq 'rename' ) { if( $cmd eq 'rename' ) {
my $new_name = join( ' ', @aa[1..@aa-1]); my $new_name = join( ' ', @aa[1..@aa-1]);
my $obj = { 'name' => $new_name, }; my $obj = { 'name' => $new_name, };
@@ -534,9 +565,9 @@ HUEDevice_Set($@)
my $result; my $result;
if( $hash->{helper}->{devtype} eq 'G' ) { if( $hash->{helper}->{devtype} eq 'G' ) {
$hash->{helper}->{update} = 1; $hash->{helper}->{update} = 1;
$result = HUEDevice_ReadFromServer($hash,$hash->{ID}."/action",\%obj); $result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/action",\%obj);
} else { } else {
$result = HUEDevice_ReadFromServer($hash,$hash->{ID}."/state",\%obj); $result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/state",\%obj);
} }
if( defined($result) && $result->{'error'} ) { if( defined($result) && $result->{'error'} ) {
@@ -544,6 +575,7 @@ HUEDevice_Set($@)
return undef; return undef;
} }
$hash->{".triggerUsed"} = 1;
return undef if( !defined($result) ); return undef if( !defined($result) );
if( $hash->{helper}->{update_timeout} == -1 ) { if( $hash->{helper}->{update_timeout} == -1 ) {
@@ -571,6 +603,7 @@ HUEDevice_Set($@)
#$list .= " dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%" if( $subtype =~ m/dimmer/ ); #$list .= " dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%" if( $subtype =~ m/dimmer/ );
$list .= " lights" if( $hash->{helper}->{devtype} eq 'G' ); $list .= " lights" if( $hash->{helper}->{devtype} eq 'G' );
$list .= " savescene scene" if( $hash->{helper}->{devtype} eq 'G' );
$list .= " rename"; $list .= " rename";
return SetExtensions($hash, $list, $name, @aa); return SetExtensions($hash, $list, $name, @aa);
@@ -826,19 +859,16 @@ HUEDevice_Parse($$)
$hash->{uniqueid} = $result->{'uniqueid'}; $hash->{uniqueid} = $result->{'uniqueid'};
if( $hash->{helper}->{devtype} eq 'G' ) { if( $hash->{helper}->{devtype} eq 'G' ) {
$hash->{STATE} = 'Initialized';
$hash->{lights} = join( ",", @{$result->{lights}} ) if( $result->{lights} ); $hash->{lights} = join( ",", @{$result->{lights}} ) if( $result->{lights} );
foreach my $id ( @{$result->{lights}} ) { if( defined($hash->{helper}->{update}) ) {
my $code = $hash->{IODev}->{NAME} ."-". $id; delete $hash->{helper}->{update};
my $chash = $modules{HUEDevice}{defptr}{$code}; fhem( "set $hash->{IODev}{NAME} statusRequest" );
return undef;
HUEDevice_GetUpdate($chash) if( defined($chash) && defined($hash->{helper}->{update}) );
} }
delete $hash->{helper}->{update};
return undef; return undef;
} }
$hash->{modelid} = $result->{modelid}; $hash->{modelid} = $result->{modelid};
@@ -935,6 +965,7 @@ HUEDevice_Parse($$)
my $reachable = $state->{reachable}?1:0; my $reachable = $state->{reachable}?1:0;
my $colormode = $state->{'colormode'}; my $colormode = $state->{'colormode'};
my $bri = $state->{'bri'}; my $bri = $state->{'bri'};
$bri = $hash->{helper}{bri} if( !defined( $bri) );
my $ct = $state->{'ct'}; my $ct = $state->{'ct'};
my $hue = $state->{'hue'}; my $hue = $state->{'hue'};
my $sat = $state->{'sat'}; my $sat = $state->{'sat'};
@@ -1007,6 +1038,9 @@ HUEDevice_Parse($$)
my $rgb = CommandGet("","$name rgb"); my $rgb = CommandGet("","$name rgb");
if( $rgb ne $hash->{helper}{rgb} ) { readingsSingleUpdate($hash,"rgb", $rgb,1); }; if( $rgb ne $hash->{helper}{rgb} ) { readingsSingleUpdate($hash,"rgb", $rgb,1); };
$hash->{helper}{rgb} = $rgb; $hash->{helper}{rgb} = $rgb;
$hash->{helper}->{update_timeout} = -1;
RemoveInternalTimer($hash);
} }
1; 1;
@@ -1107,9 +1141,12 @@ HUEDevice_Parse($$)
<li>delayedUpdate</li> <li>delayedUpdate</li>
<li>immediateUpdate</li> <li>immediateUpdate</li>
<br> <br>
<li>lights &lt;light-1&gt[ &lt;light-2&gt;..&lt;light-n&gt;]<br> <li>savescene &lt;id&gt;</li>
<li>scene</li>
<br>
<li>lights &lt;lights&gt;<br>
Only valid for groups. Changes the list of lights in this group. Only valid for groups. Changes the list of lights in this group.
The lights can be given as fhem device names or bridge device numbers.</li> The lights are given as a comma sparated list of fhem device names or bridge light numbers.</li>
<li>rename &lt;new name&gt;<br> <li>rename &lt;new name&gt;<br>
Renames the device in the bridge and changes the fhem alias.</li> Renames the device in the bridge and changes the fhem alias.</li>
<br> <br>

View File

@@ -29,7 +29,7 @@ sub LightScene_Initialize($)
$hash->{SetFn} = "LightScene_Set"; $hash->{SetFn} = "LightScene_Set";
$hash->{GetFn} = "LightScene_Get"; $hash->{GetFn} = "LightScene_Get";
$hash->{AttrFn} = "LightScene_Attr"; $hash->{AttrFn} = "LightScene_Attr";
$hash->{AttrList} = "followDevices:1,2 switchingOrder ". $readingFnAttributes; $hash->{AttrList} = "async_delay followDevices:1,2 switchingOrder ". $readingFnAttributes;
$hash->{FW_detailFn} = "LightScene_detailFn"; $hash->{FW_detailFn} = "LightScene_detailFn";
$data{FWEXT}{"/LightScene"}{FUNC} = "LightScene_CGI"; #mod $data{FWEXT}{"/LightScene"}{FUNC} = "LightScene_CGI"; #mod
@@ -77,6 +77,9 @@ sub LightScene_Define($$)
LightScene_updateHelper( $hash, AttrVal($name,"switchingOrder",undef) ); LightScene_updateHelper( $hash, AttrVal($name,"switchingOrder",undef) );
my @arr = ();
$hash->{".asyncQueue"} = \@arr;
$hash->{STATE} = 'Initialized'; $hash->{STATE} = 'Initialized';
return undef; return undef;
@@ -410,9 +413,9 @@ LightScene_Load($)
} }
sub sub
LightScene_SaveDevice($$) LightScene_SaveDevice($$;$)
{ {
my($hash,$d) = @_; my($hash,$d,$scene) = @_;
my $state = ""; my $state = "";
my $icon = undef; my $icon = undef;
@@ -490,10 +493,22 @@ LightScene_SaveDevice($$)
$state = "rgb ". $state if( $state ne "off" ); $state = "rgb ". $state if( $state ne "off" );
} elsif( $type eq 'HUEDevice' ) { } elsif( $type eq 'HUEDevice' ) {
my $subtype = AttrVal($d,"subType",""); my $subtype = AttrVal($d,"subType","");
if( $subtype eq "switch" || Value($d) eq "off" ) { if( $defs{$d}->{helper}->{devtype} eq "G" ) {
if( $scene ) {
my $id = "FHEM-$hash->{NAME}-$scene";
$state = "scene $id";
fhem( "set $d savescene $id" );
} else {
$state = "<unknown>";
}
} elsif( $subtype eq "switch" || Value($d) eq "off" ) {
$state = Value($d); $state = Value($d);
} elsif( $subtype eq "dimmer" ) { } elsif( $subtype eq "dimmer" ) {
$state = "bri ". ReadingsVal($d,'bri',"0"); $state = "bri ". ReadingsVal($d,'bri',"0");
} elsif( $subtype =~ m/color|ct/ ) { } elsif( $subtype =~ m/color|ct/ ) {
my $cm = ReadingsVal($d,"colormode",""); my $cm = ReadingsVal($d,"colormode","");
if( $cm eq "ct" ) { if( $cm eq "ct" ) {
@@ -505,6 +520,7 @@ LightScene_SaveDevice($$)
$state = "bri ". ReadingsVal($d,'bri',"0") ." : xy ". ReadingsVal($d,'xy',""); $state = "bri ". ReadingsVal($d,'bri',"0") ." : xy ". ReadingsVal($d,'xy',"");
} }
} }
} elsif( $type eq 'IT' ) { } elsif( $type eq 'IT' ) {
my $subtype = AttrVal($d,"model",""); my $subtype = AttrVal($d,"model","");
if( $subtype eq "itswitch" ) { if( $subtype eq "itswitch" ) {
@@ -535,11 +551,22 @@ LightScene_RestoreDevice($$$)
return ("",0) if( $state eq $cmd ); return ("",0) if( $state eq $cmd );
} }
my $async_delay = AttrVal($hash->{NAME}, "async_delay", undef);
my $ret; my $ret;
if( $cmd =~m/^;/ ) { if( $cmd =~m/^;/ ) {
$ret = AnalyzeCommandChain(undef,"$cmd"); if(defined($async_delay)) {
push @{$hash->{".asyncQueue"}}, $cmd;
} else {
$ret = AnalyzeCommandChain(undef,$cmd);
}
} else { } else {
$ret = CommandSet(undef,"$d $cmd"); if(defined($async_delay)) {
push @{$hash->{".asyncQueue"}}, "$d $cmd";
} else {
$ret = CommandSet(undef,"$d $cmd");
}
} }
return ($ret,1); return ($ret,1);
@@ -608,6 +635,8 @@ LightScene_Set($@)
} }
my $count = 0; my $count = 0;
my $async_delay = AttrVal($hash->{NAME}, "async_delay", undef);
my $asyncQueueLength = @{$hash->{".asyncQueue"}};
foreach my $d (@devices) { foreach my $d (@devices) {
next if(!$defs{$d}); next if(!$defs{$d});
if($defs{$d}{INSET}) { if($defs{$d}{INSET}) {
@@ -616,7 +645,7 @@ LightScene_Set($@)
} }
if( $cmd eq "save" ) { if( $cmd eq "save" ) {
my($state,$icon,$type) = LightScene_SaveDevice($hash,$d); my($state,$icon,$type) = LightScene_SaveDevice($hash,$d,$scene);
if( $icon || ref($state) eq 'ARRAY' || $type eq "SWAP_0000002200000003" || $type eq "HUEDevice" ) { if( $icon || ref($state) eq 'ARRAY' || $type eq "SWAP_0000002200000003" || $type eq "HUEDevice" ) {
my %desc; my %desc;
@@ -667,11 +696,31 @@ LightScene_Set($@)
LightScene_updateHelper( $hash, AttrVal($name,"switchingOrder",undef) ); LightScene_updateHelper( $hash, AttrVal($name,"switchingOrder",undef) );
InternalTimer(gettimeofday()+0, "LightScene_asyncQueue", $hash, 0) if( @{$hash->{".asyncQueue"}} && !$asyncQueueLength );
return $ret; return $ret;
return undef; return undef;
} }
sub
LightScene_asyncQueue(@)
{
my ($hash) = @_;
my $cmd = shift @{$hash->{".asyncQueue"}};
if(defined $cmd) {
if( $cmd =~m/^;/ ) {
AnalyzeCommandChain(undef,$cmd);
} else {
CommandSet(undef, $cmd);
}
my $async_delay = AttrVal($hash->{NAME}, "async_delay", 0);
InternalTimer(gettimeofday()+$async_delay,"LightScene_asyncQueue",$hash,0) if( @{$hash->{".asyncQueue"}} );
}
return undef;
}
sub sub
LightScene_Get($@) LightScene_Get($@)
{ {
@@ -952,6 +1001,15 @@ LightScene_editTable($) {
<a name="LightScene_Attr"></a> <a name="LightScene_Attr"></a>
<b>Attributes</b> <b>Attributes</b>
<ul> <ul>
<a name="async_delay"></a>
<li>async_delay<br>
If this attribute is defined, unfiltered set commands will not be
executed in the clients immediately. Instead, they are added to a queue
to be executed later. The set command returns immediately, whereas the
clients will be set timer-driven, one at a time. The delay between two
timercalls is given by the value of async_delay (in seconds) and may be
0 for fastest possible execution.
</li>
<li>lightSceneParamsToSave<br> <li>lightSceneParamsToSave<br>
this attribute can be set on the devices to be included in a scene. it is set to a comma separated list of readings this attribute can be set on the devices to be included in a scene. it is set to a comma separated list of readings
that will be saved. multiple readings separated by : are collated in to a single set command (this has to be supported that will be saved. multiple readings separated by : are collated in to a single set command (this has to be supported