diff --git a/fhem/CHANGED b/fhem/CHANGED index b1ef52341..62ad5b66b 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,15 @@ # 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 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 - feature: 73_km200: Improving error message in state - feature: SYSMON: FormatString for SYSMON_ShowValues. diff --git a/fhem/FHEM/30_HUEBridge.pm b/fhem/FHEM/30_HUEBridge.pm index f4920a8bc..28dcdd224 100644 --- a/fhem/FHEM/30_HUEBridge.pm +++ b/fhem/FHEM/30_HUEBridge.pm @@ -39,7 +39,7 @@ sub HUEBridge_Initialize($) $hash->{SetFn} = "HUEBridge_Set"; $hash->{GetFn} = "HUEBridge_Get"; $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 @@ -226,14 +226,39 @@ sub HUEBridge_Pair($) 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 HUEBridge_Set($@) { - my ($hash, $name, $cmd, $arg, @params) = @_; + my ($hash, $name, $cmd, @args) = @_; + my ($arg, @params) = @args; # usage check if($cmd eq 'statusRequest') { + return "usage: statusRequest" if( @args != 0 ); + $hash->{LOCAL} = 1; #RemoveInternalTimer($hash); HUEBridge_GetUpdate($hash); @@ -241,6 +266,8 @@ HUEBridge_Set($@) return undef; } elsif($cmd eq 'swupdate') { + return "usage: swupdate" if( @args != 0 ); + my $obj = { 'swupdate' => { 'updatestate' => 3, }, }; @@ -256,9 +283,13 @@ HUEBridge_Set($@) return "starting update"; } elsif($cmd eq 'autocreate') { + return "usage: autocreate" if( @args != 0 ); + return HUEBridge_Autocreate($hash,1); } elsif($cmd eq 'autodetect') { + return "usage: autodetect" if( @args != 0 ); + my $result = HUEBridge_Call($hash, undef, 'lights', undef, 'POST'); return $result->{success}{'/lights'} if( $result->{success} ); @@ -267,10 +298,14 @@ HUEBridge_Set($@) return undef; } elsif($cmd eq 'delete') { + return "usage: delete " if( @args != 1 ); + if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) { $arg = $defs{$arg}{ID}; } + return "$arg is not hue light number" if( $arg !~ m/^\d+$/ ); + my $code = $name ."-". $arg; if( defined($modules{HUEDevice}{defptr}{$code}) ) { CommandDelete( undef, "$modules{HUEDevice}{defptr}{$code}{NAME}" ); @@ -283,15 +318,10 @@ HUEBridge_Set($@) return undef; } elsif($cmd eq 'creategroup') { + return "usage: creategroup " if( @args < 2 ); - 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 $obj = { 'name' => join( ' ', @args[0..@args-2]), + 'lights' => HUEBridge_string2array($args[@args-1]), }; my $result = HUEBridge_Call($hash, undef, 'groups', $obj, 'POST'); @@ -307,7 +337,10 @@ HUEBridge_Set($@) return undef; } elsif($cmd eq 'deletegroup') { + return "usage: deletegroup " if( @args != 1 ); + 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(.*)/; $arg = $1; } @@ -318,12 +351,66 @@ HUEBridge_Set($@) 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'); return $result->{error}{description} if( $result->{error} ); return undef; + } elsif($cmd eq 'savescene') { + return "usage: savescene " 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 " 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 " 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') { + return "usage: deletewhitelist " if( @args != 1 ); my $result = HUEBridge_Call($hash, undef, "config/whitelist/$arg", undef, 'DELETE'); return $result->{error}{description} if( $result->{error} ); @@ -331,6 +418,8 @@ HUEBridge_Set($@) return undef; } elsif($cmd eq 'touchlink') { + return "usage: touchlink" if( @args != 0 ); + my $obj = { 'touchlink' => JSON::true }; my $result = HUEBridge_Call($hash, undef, 'config', $obj, 'PUT'); @@ -342,7 +431,7 @@ HUEBridge_Set($@) } 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' ); 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) ); # usage check - if($cmd eq 'devices') { + if($cmd eq 'devices' + || $cmd eq 'lights') { my $result = HUEBridge_Call($hash, undef, 'lights', undef); my $ret = ""; - foreach my $key ( sort {$a<=>$b} keys %$result ) { + 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}) ); @@ -372,7 +462,7 @@ HUEBridge_Get($@) my $result = HUEBridge_Call($hash, undef, 'groups', undef); $result->{0} = { name => 'Lightset 0', type => 'LightGroup', lights => ["ALL"] }; my $ret = ""; - foreach my $key ( sort {$a<=>$b} keys %$result ) { + 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}) ); @@ -381,16 +471,25 @@ HUEBridge_Get($@) $ret = sprintf( "%2s %-15s %-15s %-15s %s\n", "ID", "NAME", "FHEM", "TYPE", "LIGHTS" ) .$ret if( $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') { my $result = HUEBridge_Call($hash, undef, 'sensors', undef); my $ret = ""; - foreach my $key ( sort {$a<=>$b} keys %$result ) { + foreach my $key ( sort {$a<=>$b} keys %{$result} ) { my $code = $name ."-S". $key; my $fhem_name =""; $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( "%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; } elsif($cmd eq 'whitelist') { @@ -404,7 +503,7 @@ HUEBridge_Get($@) return $ret; } 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 $result = HUEBridge_Call($hash,undef, 'lights', undef); - foreach my $key ( keys %$result ) { + foreach my $key ( keys %{$result} ) { my $id= $key; my $code = $name ."-". $id; @@ -531,7 +630,7 @@ HUEBridge_Autocreate($;$) $result = HUEBridge_Call($hash,undef, 'groups', undef); $result->{0} = { name => "Lightset 0", }; - foreach my $key ( keys %$result ) { + foreach my $key ( keys %{$result} ) { my $id= $key; my $code = $name ."-G". $id; @@ -580,23 +679,38 @@ sub HUEBridge_ProcessResponse($$) $hash->{STATE} = $error; } -if( 0 ) { + if( !AttrVal( $name,'queryAfterSet', 0 ) ) { + my $successes; + my $errors; my %json = (); foreach my $item (@{$obj}) { if( my $success = $item->{success} ) { + next if( ref($success) ne 'HASH' ); foreach my $key ( keys %{$success} ) { my @l = split( '/', $key ); + next if( !$l[1] ); if( $l[1] eq 'lights' && $l[3] eq 'state' ) { $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} ) { my $msg = $error->{'description'}; Log3 $name, 3, $msg; + $errors++; } } -#Log 3, Dumper \%json; foreach my $id ( keys %json ) { my $code = $name ."-". $id; @@ -605,7 +719,9 @@ if( 0 ) { HUEDevice_Parse( $chash, $json{$id} ); } } -} + } + + #return undef if( !$errors && $successes ); return ($obj->[0]); } @@ -698,7 +814,8 @@ HUEBridge_HTTP_Call($$$;$) } elsif($ret eq '') { return undef; } elsif($ret =~ /^error:(\d){3}$/) { - return "HTTP Error Code " . $1; + my %result = { error => "HTTP Error Code $1" }; + return \%result; } if( !$ret ) { @@ -807,11 +924,15 @@ HUEBridge_dispatch($$$;$) return undef; } + my $queryAfterSet = AttrVal( $name,'queryAfterSet', 0 ); + $json = from_json($data) if( !$json ); my $type = $param->{type}; if( ref($json) eq 'ARRAY' ) { + HUEBridge_ProcessResponse($hash,$json) if( !$queryAfterSet ); + if( defined($json->[0]->{error})) { my $error = $json->[0]->{error}->{'description'}; @@ -892,13 +1013,15 @@ HUEBridge_dispatch($$$;$) 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 ); + if( $queryAfterSet ) { + 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$/ ) { @@ -1000,7 +1123,8 @@ HUEBridge_HTTP_Request($$$@) } undef $conn; if($header[0] =~ /^[^ ]+ ([\d]{3})/ && $1 != 200) { - return "error:" . $1; + my %result = { error => "error: $1" }; + return \%result; } return $ret; } @@ -1057,6 +1181,8 @@ HUEBridge_HTTP_Request($$$@) list the devices known to the bridge.
  • groups
    list the groups known to the bridge.
  • +
  • scenes
    + list the scenes known to the bridge.
  • sensors
    list the sensors known to the bridge.
  • whitelist
    @@ -1074,11 +1200,18 @@ HUEBridge_HTTP_Request($$$@) can be created by set autocreate.
  • delete <name>|<id>
    Deletes the given device in the bridge and deletes the associated fhem device.
  • -
  • creategroup <name> <light-1>[ <light-2>..<lignt-n>]
    - Create a group out of <light-1>-<light-n> in the bridge. - The lights can be given as fhem device names or bridge device numbers.
  • +
  • creategroup <name> <lights>
    + Create a group out of <lights> in the bridge. + The lights are given as a comma sparated list of fhem device names or bridge light numbers.
  • deletegroup <name>|<id>
    Deletes the given group in the bridge and deletes the associated fhem device.
  • +
  • savescene <id> <name> <lights>
    + Create a scene from the current state of <lights> in the bridge. + The lights are given as a comma sparated list of fhem device names or bridge light numbers.
  • +
  • scene <id>
    + Recalls the scene with the given id.
  • +
  • modifyscene <id> <light> <light-args>
    + Modifys the given scene in the bridge.
  • deletwhitelist <key>
    Deletes the given key from the whitelist in the bridge.
  • touchlink
    diff --git a/fhem/FHEM/31_HUEDevice.pm b/fhem/FHEM/31_HUEDevice.pm index a929c8283..2d878b510 100644 --- a/fhem/FHEM/31_HUEDevice.pm +++ b/fhem/FHEM/31_HUEDevice.pm @@ -16,6 +16,9 @@ use POSIX; use JSON; 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 my %hueModels = ( @@ -270,7 +273,7 @@ HUEDevice_SetParam($$@) if( $cmd eq "color" ) { $value = int(1000000/$value); $cmd = 'ct'; - } elsif( $cmd eq "toggle" ) { + } elsif( $name && $cmd eq "toggle" ) { $cmd = ReadingsVal($name,"state","on") eq "off" ? "on" :"off"; } elsif( $cmd =~ m/^dim(\d+)/ ) { $value2 = $value; @@ -290,7 +293,7 @@ HUEDevice_SetParam($$@) if($cmd eq 'on') { $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) ); } elsif($cmd eq 'off') { @@ -318,13 +321,13 @@ HUEDevice_SetParam($$@) $obj->{'bri'} = 0+$value; $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) ) { $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} ); $obj->{'bri_inc'} = 25; $obj->{'bri_inc'} = $value if( defined($value) ); $obj->{'transitiontime'} = 1; - $defs{$name}->{helper}->{update_timeout} = 0; + #$defs{$name}->{helper}->{update_timeout} = 0; } else { my $bri = ReadingsVal($name,"bri","0"); $bri += 25; @@ -336,13 +339,13 @@ HUEDevice_SetParam($$@) $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) ) { $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} ); $obj->{'bri_inc'} = -25; $obj->{'bri_inc'} = -$value if( defined($value) ); $obj->{'transitiontime'} = 1; - $defs{$name}->{helper}->{update_timeout} = 0; + #$defs{$name}->{helper}->{update_timeout} = 0; } else { my $bri = ReadingsVal($name,"bri","0"); $bri -= 25; @@ -375,7 +378,7 @@ HUEDevice_SetParam($$@) } elsif( $cmd eq "rgb" && $value =~ m/^(..)(..)(..)/) { 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); $obj->{'on'} = JSON::true; @@ -426,11 +429,11 @@ HUEDevice_SetParam($$@) $obj->{'effect'} = $value; } elsif( $cmd eq "transitiontime" ) { $obj->{'transitiontime'} = 0+$value; - } elsif( $cmd eq "delayedUpdate" ) { + } elsif( $name && $cmd eq "delayedUpdate" ) { $defs{$name}->{helper}->{update_timeout} = 1; - } elsif( $cmd eq "immediateUpdate" ) { + } elsif( $name && $cmd eq "immediateUpdate" ) { $defs{$name}->{helper}->{update_timeout} = 0; - } elsif( $cmd eq "noUpdate" ) { + } elsif( $name && $cmd eq "noUpdate" ) { $defs{$name}->{helper}->{update_timeout} = -1; } else { return 0; @@ -443,20 +446,17 @@ sub HUEDevice_Set($@) { my ($hash, $name, @aa) = @_; + my ($cmd, @args) = @aa; 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( $aa[0] eq 'lights' ) { - my @lights = (); - for my $param (@aa[1..@aa-1]) { - $param = $defs{$param}{ID} if( defined $defs{$param} && $defs{$param}{TYPE} eq 'HUEDevice' ); - push( @lights, $param ); - } + if( $cmd eq 'lights' ) { + return "usage: lights " if( @args != 1 ); - my $obj = { 'lights' => \@lights, }; + my $obj = { 'lights' => HUEBridge_string2array($args[0]), }; my $result = HUEDevice_ReadFromServer($hash,$hash->{ID},$obj); if( $result->{success} ) { @@ -465,20 +465,51 @@ HUEDevice_Set($@) } return $result->{error}{description} if( $result->{error} ); + return undef; + } elsif( $cmd eq 'savescene' ) { + return "usage: savescene " if( @args != 1 ); + + return fhem( "set $hash->{IODev}{NAME} savescene $aa[1] $aa[1] $hash->{NAME}" ); + + } elsif( $cmd eq 'scene' ) { + return "usage: scene " 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' ) { - if( $aa[0] eq "statusRequest" ) { + if( $cmd eq "statusRequest" ) { RemoveInternalTimer($hash); HUEDevice_GetUpdate($hash); 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 $obj = { 'name' => $new_name, }; @@ -534,9 +565,9 @@ HUEDevice_Set($@) my $result; if( $hash->{helper}->{devtype} eq 'G' ) { $hash->{helper}->{update} = 1; - $result = HUEDevice_ReadFromServer($hash,$hash->{ID}."/action",\%obj); + $result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/action",\%obj); } else { - $result = HUEDevice_ReadFromServer($hash,$hash->{ID}."/state",\%obj); + $result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/state",\%obj); } if( defined($result) && $result->{'error'} ) { @@ -544,6 +575,7 @@ HUEDevice_Set($@) return undef; } + $hash->{".triggerUsed"} = 1; return undef if( !defined($result) ); 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 .= " lights" if( $hash->{helper}->{devtype} eq 'G' ); + $list .= " savescene scene" if( $hash->{helper}->{devtype} eq 'G' ); $list .= " rename"; return SetExtensions($hash, $list, $name, @aa); @@ -826,19 +859,16 @@ HUEDevice_Parse($$) $hash->{uniqueid} = $result->{'uniqueid'}; if( $hash->{helper}->{devtype} eq 'G' ) { + $hash->{STATE} = 'Initialized'; $hash->{lights} = join( ",", @{$result->{lights}} ) if( $result->{lights} ); - foreach my $id ( @{$result->{lights}} ) { - my $code = $hash->{IODev}->{NAME} ."-". $id; - my $chash = $modules{HUEDevice}{defptr}{$code}; - - HUEDevice_GetUpdate($chash) if( defined($chash) && defined($hash->{helper}->{update}) ); + if( defined($hash->{helper}->{update}) ) { + delete $hash->{helper}->{update}; + fhem( "set $hash->{IODev}{NAME} statusRequest" ); + return undef; } - delete $hash->{helper}->{update}; - return undef; - } $hash->{modelid} = $result->{modelid}; @@ -935,6 +965,7 @@ HUEDevice_Parse($$) my $reachable = $state->{reachable}?1:0; my $colormode = $state->{'colormode'}; my $bri = $state->{'bri'}; + $bri = $hash->{helper}{bri} if( !defined( $bri) ); my $ct = $state->{'ct'}; my $hue = $state->{'hue'}; my $sat = $state->{'sat'}; @@ -1007,6 +1038,9 @@ HUEDevice_Parse($$) my $rgb = CommandGet("","$name rgb"); if( $rgb ne $hash->{helper}{rgb} ) { readingsSingleUpdate($hash,"rgb", $rgb,1); }; $hash->{helper}{rgb} = $rgb; + + $hash->{helper}->{update_timeout} = -1; + RemoveInternalTimer($hash); } 1; @@ -1107,9 +1141,12 @@ HUEDevice_Parse($$)
  • delayedUpdate
  • immediateUpdate

  • -
  • lights <light-1>[ <light-2>..<light-n>]
    +
  • savescene <id>
  • +
  • scene
  • +
    +
  • lights <lights>
    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.
  • + The lights are given as a comma sparated list of fhem device names or bridge light numbers.
  • rename <new name>
    Renames the device in the bridge and changes the fhem alias.

  • diff --git a/fhem/FHEM/31_LightScene.pm b/fhem/FHEM/31_LightScene.pm index c2f54ea12..e2e26d339 100644 --- a/fhem/FHEM/31_LightScene.pm +++ b/fhem/FHEM/31_LightScene.pm @@ -29,7 +29,7 @@ sub LightScene_Initialize($) $hash->{SetFn} = "LightScene_Set"; $hash->{GetFn} = "LightScene_Get"; $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"; $data{FWEXT}{"/LightScene"}{FUNC} = "LightScene_CGI"; #mod @@ -77,6 +77,9 @@ sub LightScene_Define($$) LightScene_updateHelper( $hash, AttrVal($name,"switchingOrder",undef) ); + my @arr = (); + $hash->{".asyncQueue"} = \@arr; + $hash->{STATE} = 'Initialized'; return undef; @@ -410,9 +413,9 @@ LightScene_Load($) } sub -LightScene_SaveDevice($$) +LightScene_SaveDevice($$;$) { - my($hash,$d) = @_; + my($hash,$d,$scene) = @_; my $state = ""; my $icon = undef; @@ -490,10 +493,22 @@ LightScene_SaveDevice($$) $state = "rgb ". $state if( $state ne "off" ); } elsif( $type eq 'HUEDevice' ) { 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 = ""; + } + + } elsif( $subtype eq "switch" || Value($d) eq "off" ) { $state = Value($d); + } elsif( $subtype eq "dimmer" ) { $state = "bri ". ReadingsVal($d,'bri',"0"); + } elsif( $subtype =~ m/color|ct/ ) { my $cm = ReadingsVal($d,"colormode",""); if( $cm eq "ct" ) { @@ -505,6 +520,7 @@ LightScene_SaveDevice($$) $state = "bri ". ReadingsVal($d,'bri',"0") ." : xy ". ReadingsVal($d,'xy',""); } } + } elsif( $type eq 'IT' ) { my $subtype = AttrVal($d,"model",""); if( $subtype eq "itswitch" ) { @@ -535,11 +551,22 @@ LightScene_RestoreDevice($$$) return ("",0) if( $state eq $cmd ); } + my $async_delay = AttrVal($hash->{NAME}, "async_delay", undef); my $ret; if( $cmd =~m/^;/ ) { - $ret = AnalyzeCommandChain(undef,"$cmd"); + if(defined($async_delay)) { + push @{$hash->{".asyncQueue"}}, $cmd; + } else { + $ret = AnalyzeCommandChain(undef,$cmd); + } + } else { - $ret = CommandSet(undef,"$d $cmd"); + if(defined($async_delay)) { + push @{$hash->{".asyncQueue"}}, "$d $cmd"; + } else { + $ret = CommandSet(undef,"$d $cmd"); + } + } return ($ret,1); @@ -608,6 +635,8 @@ LightScene_Set($@) } my $count = 0; + my $async_delay = AttrVal($hash->{NAME}, "async_delay", undef); + my $asyncQueueLength = @{$hash->{".asyncQueue"}}; foreach my $d (@devices) { next if(!$defs{$d}); if($defs{$d}{INSET}) { @@ -616,7 +645,7 @@ LightScene_Set($@) } 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" ) { my %desc; @@ -667,11 +696,31 @@ LightScene_Set($@) LightScene_updateHelper( $hash, AttrVal($name,"switchingOrder",undef) ); + InternalTimer(gettimeofday()+0, "LightScene_asyncQueue", $hash, 0) if( @{$hash->{".asyncQueue"}} && !$asyncQueueLength ); + return $ret; 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 LightScene_Get($@) { @@ -952,6 +1001,15 @@ LightScene_editTable($) { Attributes
      + +
    • async_delay
      + 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. +
    • lightSceneParamsToSave
      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