2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-10 09:16:53 +00:00

30_HUEBridge.pm: suport for eventstream api (push events)

git-svn-id: https://svn.fhem.de/fhem/trunk@25506 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
justme-1968 2022-01-19 15:19:26 +00:00
parent e8a5081096
commit d71fe570b4
3 changed files with 586 additions and 43 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.
- feature: 30_HUEBridge: support eventstream api (push events)
- change: 70_DENON_AVR: added soundformat strings. patch by All-Ex - change: 70_DENON_AVR: added soundformat strings. patch by All-Ex
- change: 50_Signalbot: link improved, reply command added, bugfixes - change: 50_Signalbot: link improved, reply command added, bugfixes
- change: 93_DbRep: new design of sqlCmdHistory, minor fixes - change: 93_DbRep: new design of sqlCmdHistory, minor fixes

View File

@ -12,7 +12,8 @@ use warnings;
use FHEM::Meta; use FHEM::Meta;
use POSIX; #use POSIX;
use Time::HiRes qw(gettimeofday);
use JSON; use JSON;
use Data::Dumper; use Data::Dumper;
@ -326,6 +327,9 @@ sub HUEBridge_Undefine($$)
{ {
my ($hash,$arg) = @_; my ($hash,$arg) = @_;
HUEBridge_closeWebsocket($hash);
HUEBridge_closeEventStream($hash);
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
return undef; return undef;
} }
@ -345,7 +349,8 @@ HUEBridge_hash2header($)
return $header; return $header;
} }
sub HUEBridge_closeWebsocket($) sub
HUEBridge_closeWebsocket($)
{ {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -361,7 +366,8 @@ sub HUEBridge_closeWebsocket($)
delete($hash->{PORT}); delete($hash->{PORT});
} }
sub HUEBridge_openWebsocket($) sub
HUEBridge_openWebsocket($)
{ {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -403,7 +409,65 @@ sub HUEBridge_openWebsocket($)
} }
} }
sub HUEBridge_fillBridgeInfo($$) sub
HUEBridge_closeEventStream($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash, "HUEBridge_openEventStream" );
return if( !defined($hash->{helper}{HTTP_CONNECTION}) );
$hash->{EventStream} = 'closing';
Log3 $name, 4, "name: EventStream: $hash->{EventStream}";
HttpUtils_Close( $hash->{helper}{HTTP_CONNECTION} );
delete $hash->{helper}{HTTP_CONNECTION};
delete $hash->{buf};
delete($hash->{EventStream});
Log3 $name, 4, "name: EventStream: closed";
}
sub
HUEBridge_openEventStream($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
my $lastID;
$lastID = $hash->{helper}{HTTP_CONNECTION}{lastID} if( defined($hash->{helper}{HTTP_CONNECTION}) );
HUEBridge_closeEventStream($hash);
$hash->{EventStream} = 'connecting';
Log3 $name, 4, "name: EventStream: $hash->{EventStream}";
my $params = {
url => "https://$hash->{host}/eventstream/clip/v2",
httpversion => '1.1',
method => 'GET',
timeout => 60*60,
incrementalTimeout => 1,
noshutdown => 1,
keepalive => 1,
header => { Accept => 'text/event-stream',
'HUE-Application-Key' => $attr{$name}{key}, },
type => 'event',
hash => $hash,
callback => \&HUEBridge_dispatch,
};
$params->{header}{'Last-Event-ID'} = $lastID if( $lastID );
$hash->{helper}{HTTP_CONNECTION} = {};
map { $hash->{helper}{HTTP_CONNECTION}{$_} = $params->{$_} } keys %{$params};
HttpUtils_NonblockingGet( $hash->{helper}{HTTP_CONNECTION} );
}
sub
HUEBridge_fillBridgeInfo($$)
{ {
my ($hash,$config) = @_; my ($hash,$config) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -413,10 +477,11 @@ sub HUEBridge_fillBridgeInfo($$)
$hash->{bridgeid} = $config->{bridgeid}; $hash->{bridgeid} = $config->{bridgeid};
$hash->{swversion} = $config->{swversion}; $hash->{swversion} = $config->{swversion};
$hash->{apiversion} = $config->{apiversion}; $hash->{apiversion} = $config->{apiversion};
$hash->{zigbeechannel} = $config->{zigbeechannel};
if( defined($config->{websocketport}) ) { delete $hash->{has_v2_api};
$hash->{websocketport} = $config->{websocketport}; if( $hash->{swversion} >= 1948086000 ) {
HUEBridge_openWebsocket($hash) if( !defined($hash->{CD}) ); $hash->{has_v2_api} = 1;
} }
if( $hash->{apiversion} ) { if( $hash->{apiversion} ) {
@ -424,10 +489,22 @@ sub HUEBridge_fillBridgeInfo($$)
$hash->{helper}{apiversion} = ($l[0] << 16) + ($l[1] << 8) + $l[2]; $hash->{helper}{apiversion} = ($l[0] << 16) + ($l[1] << 8) + $l[2];
} }
if( !defined($config->{'linkbutton'})
if( defined($config->{websocketport}) ) {
$hash->{websocketport} = $config->{websocketport};
HUEBridge_openWebsocket($hash) if( !defined($hash->{CD}) );
} elsif( $hash->{has_v2_api} ) {
HUEBridge_openEventStream($hash) if( !defined($hash->{helper}{HTTP_CONNECTION}) );
}
if( $hash->{modelid}
&& !defined($config->{'linkbutton'})
&& !defined($attr{$name}{icon}) ) { && !defined($attr{$name}{icon}) ) {
$attr{$name}{icon} = 'hue_filled_bridge_v1' if( $hash->{modelid} && $hash->{modelid} eq 'BSB001' ); $attr{$name}{icon} = 'hue_filled_bridge_v1' if( $hash->{modelid} eq 'BSB001' );
$attr{$name}{icon} = 'hue_filled_bridge_v2' if( $hash->{modelid} && $hash->{modelid} eq 'BSB002' ); $attr{$name}{icon} = 'hue_filled_bridge_v2' if( $hash->{modelid} eq 'BSB002' );
} }
} }
@ -447,12 +524,14 @@ HUEBridge_OpenDev($)
if( defined($err) && $err ) { if( defined($err) && $err ) {
Log3 $name, 2, "HUEBridge_OpenDev: error reading description: ". $err; Log3 $name, 2, "HUEBridge_OpenDev: error reading description: ". $err;
} else { } else {
Log3 $name, 5, "HUEBridge_OpenDev: got description: $ret"; Log3 $name, 5, "HUEBridge_OpenDev: got description: $ret";
$ret =~ m/<modelName>([^<]*)/; $ret =~ m/<modelName>([^<]*)/;
$hash->{modelName} = $1; $hash->{modelName} = $1;
$ret =~ m/<manufacturer>([^<]*)/; $ret =~ m/<manufacturer>([^<]*)/;
$hash->{manufacturer} = $1; $hash->{manufacturer} = $1;
} }
my $result = HUEBridge_Call($hash, undef, 'config', undef); my $result = HUEBridge_Call($hash, undef, 'config', undef);
@ -468,15 +547,29 @@ HUEBridge_OpenDev($)
HUEBridge_Pair($hash); HUEBridge_Pair($hash);
return; return;
} else {
HUEBridge_fillBridgeInfo($hash, $result);
} }
$hash->{mac} = $result->{mac}; $hash->{mac} = $result->{mac};
#$hash->{bridgeid} = $result->{bridgeid};
my $params = {
url => "https://$hash->{host}/auth/v1",
#httpversion => '1.1',
method => 'GET',
timeout => 5,
header => { 'HUE-Application-Key' => $attr{$name}{key}, },
type => 'application id',
hash => $hash,
callback => \&HUEBridge_dispatch,
};
HttpUtils_NonblockingGet( $params );
readingsSingleUpdate($hash, 'state', 'connected', 1 ); readingsSingleUpdate($hash, 'state', 'connected', 1 );
HUEBridge_GetUpdate($hash);
HUEBridge_Autocreate($hash); HUEBridge_Autocreate($hash);
HUEBridge_GetUpdate($hash);
return undef; return undef;
} }
@ -619,7 +712,13 @@ HUEBridge_Set($@)
#return "$name: not connected" if( $hash->{STATE} ne 'connected' ); #return "$name: not connected" if( $hash->{STATE} ne 'connected' );
# usage check # usage check
if($cmd eq 'statusRequest') { if($cmd eq 'reconnect') {
#HUEBridge_OpenDev( $hash );
HUEBridge_openEventStream( $hash );
return undef;
} elsif($cmd eq 'statusRequest') {
return "usage: statusRequest" if( @args != 0 ); return "usage: statusRequest" if( @args != 0 );
$hash->{LOCAL} = 1; $hash->{LOCAL} = 1;
@ -997,6 +1096,81 @@ HUEBridge_Set($@)
readingsSingleUpdate($hash, 'state', 'inactive', 1 ); readingsSingleUpdate($hash, 'state', 'inactive', 1 );
return undef; return undef;
} elsif($cmd eq 'v2effect' ) {
my $params = {
url => "https://$hash->{host}/clip/v2/resource/light/$arg",
method => 'PUT',
timeout => 5,
header => { 'HUE-Application-Key' => $attr{$name}{key}, },
type => $cmd,
hash => $hash,
callback => \&HUEBridge_dispatch,
data => '{"on":{"on": true}}',
#data => '{"effect":{"effect": "candle"}}',
#data => '{"effect":{"effect": "breathe"}}',
};
my($err,$data) = HttpUtils_BlockingGet( $params );
if( !$data ) {
Log3 $name, 2, "$name: empty answer received for $cmd";
return undef;
} elsif( $data =~ m'HTTP/1.1 200 OK' ) {
Log3 $name, 4, "$name: empty answer received for $cmd";
return undef;
} elsif( $data !~ m/^[\[{].*[\]}]$/ ) {
#Log3 $name, 2, "$name: invalid json detected for $cmd: $data";
#return undef;
}
Log3 $name, 4, "$name: got: $data";
my $json = eval { JSON->new->utf8(0)->decode($data) };
Log3 $name, 2, "$name: json error: $@ in $data" if( $@ );
return undef if( !$json );
Log3 $name, 1, "$name: error: ". Dumper $json->{errors} if( scalar @{$json->{errors}} );
return Dumper $json if( scalar @{$json->{errors}} );
return;
} elsif($cmd eq 'v2scene' ) {
my $params = {
url => "https://$hash->{host}/clip/v2/resource/scene/$arg",
method => 'PUT',
timeout => 5,
header => { 'HUE-Application-Key' => $attr{$name}{key}, },
type => $cmd,
hash => $hash,
callback => \&HUEBridge_dispatch,
#data => '{ "recall": { "action": "active" } }',
data => '{ "recall": { "action": "dynamic_palette" } }',
};
my($err,$data) = HttpUtils_BlockingGet( $params );
if( !$data ) {
Log3 $name, 2, "$name: empty answer received for $cmd";
return undef;
} elsif( $data =~ m'HTTP/1.1 200 OK' ) {
Log3 $name, 4, "$name: empty answer received for $cmd";
return undef;
} elsif( $data !~ m/^[\[{].*[\]}]$/ ) {
#Log3 $name, 2, "$name: invalid json detected for $cmd: $data";
#return undef;
}
Log3 $name, 4, "$name: got: $data";
my $json = eval { JSON->new->utf8(0)->decode($data) };
Log3 $name, 2, "$name: json error: $@ in $data" if( $@ );
return undef if( !$json );
Log3 $name, 1, "$name: error: ". Dumper $json->{errors} if( scalar @{$json->{errors}} );
return Dumper $json if( scalar @{$json->{errors}} );
return;
} else { } else {
my $list = "active inactive delete creategroup deletegroup savescene deletescene modifyscene"; my $list = "active inactive delete creategroup deletegroup savescene deletescene modifyscene";
@ -1025,10 +1199,28 @@ HUEBridge_Set($@)
$list .= " swupdate:noArg" if( defined($hash->{updatestate}) && $hash->{updatestate} =~ '^2' ); $list .= " swupdate:noArg" if( defined($hash->{updatestate}) && $hash->{updatestate} =~ '^2' );
$list .= " createrule updaterule updateschedule enableschedule disableschedule deleterule createsensor deletesensor configsensor setsensor updatesensor deletewhitelist touchlink:noArg checkforupdate:noArg autodetect:noArg autocreate:noArg statusRequest:noArg"; $list .= " createrule updaterule updateschedule enableschedule disableschedule deleterule createsensor deletesensor configsensor setsensor updatesensor deletewhitelist touchlink:noArg checkforupdate:noArg autodetect:noArg autocreate:noArg statusRequest:noArg";
if( $hash->{has_v2_api} ) {
$list .= " v2scene";
}
return "Unknown argument $cmd, choose one of $list"; return "Unknown argument $cmd, choose one of $list";
} }
} }
sub
HUEBridge_nameOfResource($$)
{
my ($hash, $id) = @_;
my $name = $hash->{NAME};
return "$name: v2 api not supported" if( !$hash->{has_v2_api} );
if( my $resource = $hash->{helper}{resource}{by_id}{$id} ) {
return $resource->{metadata}{name};
}
return '<unknown>';
}
sub sub
HUEBridge_Get($@) HUEBridge_Get($@)
{ {
@ -1177,12 +1369,13 @@ HUEBridge_Get($@)
$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}) );
$fhem_name = "" if( !$fhem_name ); $fhem_name = "" if( !$fhem_name );
$ret .= sprintf( "%2i: %-15s %-15s %-20s", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type} ); $ret .= sprintf( "%2i: %-15s %-15s %-20s", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type} );
$ret .= sprintf( " %s", encode_json($result->{$key}{state}) ) if( $arg && $arg eq 'detail' ); $ret .= sprintf( "\n%-56s %s", '', encode_json($result->{$key}{state}) ) if( $arg && $arg eq 'detail' );
$ret .= sprintf( "\n%-56s %s", '', encode_json($result->{$key}{config}) ) if( $arg && $arg eq 'detail' ); $ret .= sprintf( "\n%-56s %s", '', encode_json($result->{$key}{config}) ) if( $arg && $arg eq 'detail' );
$ret .= sprintf( "\n%-56s %s", '', encode_json($result->{$key}{capabilities}) ) if( $arg && $arg eq 'detail' );
$ret .= "\n"; $ret .= "\n";
} }
if( $arg && $arg eq 'detail' ) { if( $arg && $arg eq 'detail' ) {
$ret = sprintf( "%2s %-15s %-15s %-20s %s\n", "ID", "NAME", "FHEM", "TYPE", "STATE,CONFIG" ) .$ret if( $ret ); $ret = sprintf( "%2s %-15s %-15s %-20s %s\n", "ID", "NAME", "FHEM", "TYPE", "STATE,CONFIG,CAPABILITIES" ) .$ret if( $ret );
} else { } else {
$ret = sprintf( "%2s %-15s %-15s %-20s\n", "ID", "NAME", "FHEM", "TYPE" ) .$ret if( $ret ); $ret = sprintf( "%2s %-15s %-15s %-20s\n", "ID", "NAME", "FHEM", "TYPE" ) .$ret if( $ret );
} }
@ -1218,12 +1411,50 @@ HUEBridge_Get($@)
$ret = sprintf( "%2s %-25s %-15s %s\t%s\n", "ID", "NAME", "FHEM", "MODE", "CONFIGURED" ) .$ret if( $ret ); $ret = sprintf( "%2s %-25s %-15s %s\t%s\n", "ID", "NAME", "FHEM", "MODE", "CONFIGURED" ) .$ret if( $ret );
return $ret; return $ret;
} elsif($cmd eq 'v2resource' ) {
if( $arg ) {
my $ret;
foreach my $entry ( values %{$hash->{helper}{resource}{by_id}} ) {
$ret .= Dumper $entry if( $entry->{id_v1} =~ /$arg$/ );
}
foreach my $entry ( values %{$hash->{helper}{resource}{by_id}} ) {
$ret .= Dumper $entry if( $entry->{type} eq $arg );
}
return $ret if( $ret );
return Dumper $hash->{helper}{resource}{by_id}{$arg};
}
return Dumper $hash->{helper}{resource};
} elsif($cmd eq 'v2resourcetypes' ) {
my %result;
foreach my $entry ( values %{$hash->{helper}{resource}{by_id}} ) {
next if( $arg && $arg ne $entry->{type} );
$result{$entry->{id}} = 1 if( $arg );
$result{$entry->{type}} = 1 if( !$arg );
}
return join( "\n", keys %result );
} elsif($cmd eq 'v2scenes' ) {
my $ret;
foreach my $entry ( values %{$hash->{helper}{resource}{by_id}} ) {
next if( $entry->{type} ne 'scene' );
$ret .= sprintf( "%-36s %-25s %-10s", $entry->{id}, $entry->{metadata}{name}, HUEBridge_nameOfResource($hash,$entry->{group}{rid}) );
$ret .= "\n";
}
$ret = sprintf( "%-36s %-25s %-15s %s\t%s\n", "ID", "NAME", "ROOM", "", "" ) .$ret if( $ret );
return $ret;
} else { } else {
my $list = "lights:noArg groups:noArg scenes:noArg rule rules:noArg sensors:noArg schedules:noArg whitelist:noArg"; my $list = "lights:noArg groups:noArg scenes:noArg rule rules:noArg sensors:noArg schedules:noArg whitelist:noArg";
if( $hash->{helper}{apiversion} && $hash->{helper}{apiversion} >= (1<<16) + (26<<8) ) { if( $hash->{helper}{apiversion} && $hash->{helper}{apiversion} >= (1<<16) + (26<<8) ) {
$list .= " startup:noArg"; $list .= " startup:noArg";
} }
if( $hash->{has_v2_api} ) {
$list .= " v2resource v2resourcetypes v2scenes";
}
return "Unknown argument $cmd, choose one of $list"; return "Unknown argument $cmd, choose one of $list";
} }
} }
@ -1241,20 +1472,28 @@ HUEBridge_GetUpdate($)
if( $hash->{websocketport} && !$hash->{PORT} ) { if( $hash->{websocketport} && !$hash->{PORT} ) {
HUEBridge_openWebsocket($hash); HUEBridge_openWebsocket($hash);
} elsif( $hash->{has_v2_api} ) {
HUEBridge_openEventStream($hash) if( !defined($hash->{helper}{HTTP_CONNECTION}) );
} }
my $type; my $type;
my $result; my $result;
my $poll_devices = AttrVal($name, "pollDevices", 1); my $poll_devices = AttrVal($name, "pollDevices", 2);
if( $poll_devices ) { if( $poll_devices ) {
my ($now) = gettimeofday(); my ($now) = gettimeofday();
if( $poll_devices > 1 || $hash->{LOCAL} || $now - $hash->{helper}{last_config_timestamp} > 300 ) { if( $poll_devices > 1 || $hash->{LOCAL} || !$hash->{helper}{last_config_timestamp}
|| $now - $hash->{helper}{last_config_timestamp} > 300 ) {
$result = HUEBridge_Call($hash, $hash, undef, undef); $result = HUEBridge_Call($hash, $hash, undef, undef);
$hash->{helper}{last_config_timestamp} = $now; $hash->{helper}{last_config_timestamp} = $now;
} else { } else {
$type = 'lights'; $type = 'lights';
$result = HUEBridge_Call($hash, $hash, 'lights', undef); $result = HUEBridge_Call($hash, $hash, 'lights', undef);
} }
} else { } else {
$type = 'config'; $type = 'config';
$result = HUEBridge_Call($hash, $hash, 'config', undef); $result = HUEBridge_Call($hash, $hash, 'config', undef);
@ -1313,6 +1552,8 @@ HUEBridge_updateGroups($$)
foreach my $chash ( values %{$groups} ) { foreach my $chash ( values %{$groups} ) {
my $count = 0; my $count = 0;
my %readings; my %readings;
$readings{all_on} = 1;
$readings{any_on} = 0;
my ($hue,$sat,$bri); my ($hue,$sat,$bri);
foreach my $light ( split(',', $chash->{lights}) ) { foreach my $light ( split(',', $chash->{lights}) ) {
next if( !$light ); next if( !$light );
@ -1371,6 +1612,9 @@ HUEBridge_updateGroups($$)
$readings{on} |= ($current->{on}?'1':'0'); $readings{on} |= ($current->{on}?'1':'0');
$readings{all_on} = 0 if( !($current->{on}?'1':'0') );
$readings{any_on} |= ($current->{on}?'1':'0');
if( AttrVal($lhash->{NAME}, 'ignoreReachable', 0) ) { if( AttrVal($lhash->{NAME}, 'ignoreReachable', 0) ) {
$readings{reachable} |= 1; $readings{reachable} |= 1;
} else { } else {
@ -1396,6 +1640,8 @@ HUEBridge_updateGroups($$)
++$count; ++$count;
} }
$readings{all_on} = 0 if( !$count );
if( AttrVal($name, 'ignoreReachable', 0) ) { if( AttrVal($name, 'ignoreReachable', 0) ) {
delete $readings{reachable}; delete $readings{reachable};
} }
@ -1459,7 +1705,7 @@ HUEBridge_Parse($$)
my($hash,$config) = @_; my($hash,$config) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 $name, 4, "parse status message for $name"; Log3 $name, 4, "$name: parse status message";
#Log3 $name, 5, Dumper $config; #Log3 $name, 5, Dumper $config;
$hash->{helper}{lights} = $config->{lights} if( $config->{lights} ); $hash->{helper}{lights} = $config->{lights} if( $config->{lights} );
@ -1470,8 +1716,6 @@ HUEBridge_Parse($$)
$config = $config->{config} if( defined($config->{config}) ); $config = $config->{config} if( defined($config->{config}) );
HUEBridge_fillBridgeInfo($hash, $config); HUEBridge_fillBridgeInfo($hash, $config);
$hash->{zigbeechannel} = $config->{zigbeechannel};
if( my $utc = $config->{UTC} ) { if( my $utc = $config->{UTC} ) {
substr( $utc, 10, 1, '_' ); substr( $utc, 10, 1, '_' );
@ -1526,11 +1770,10 @@ HUEBridge_Autocreate($;$$)
} }
} }
my $autocreated = 0; my @ignored = (0, 0, 0);
my @created = (0, 0, 0);
my $result = HUEBridge_Call($hash,undef, 'lights', undef); my $result = HUEBridge_Call($hash,undef, 'lights', undef);
foreach my $key ( keys %{$result} ) { foreach my $id ( sort {$a<=>$b} keys %{$result} ) {
my $id= $key;
my $code = $name ."-". $id; my $code = $name ."-". $id;
if( defined($modules{HUEDevice}{defptr}{$code}) ) { if( defined($modules{HUEDevice}{defptr}{$code}) ) {
Log3 $name, 5, "$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}'";
@ -1555,14 +1798,18 @@ HUEBridge_Autocreate($;$$)
HUEDeviceSetIcon($devname); HUEDeviceSetIcon($devname);
$defs{$devname}{helper}{fromAutocreate} = 1 ; $defs{$devname}{helper}{fromAutocreate} = 1 ;
$autocreated++; $created[0]++;
} }
} }
$result = HUEBridge_Call($hash,undef, 'groups', undef); $result = HUEBridge_Call($hash,undef, 'groups', undef);
$result->{0} = { name => "Lightset 0", }; $result->{0} = { name => "Lightset 0", type => 'LightGroup' };
foreach my $key ( keys %{$result} ) { foreach my $id ( sort {$a<=>$b} keys %{$result} ) {
my $id= $key; if( $result->{$id}{type} eq 'Entertainment' ) {
Log3 $name, 4, "$name: ignoring group $id ($result->{$id}{name}) of type $result->{$id}{type} in autocreate";
$ignored[1]++;
next;
}
my $code = $name ."-G". $id; my $code = $name ."-G". $id;
if( defined($modules{HUEDevice}{defptr}{$code}) ) { if( defined($modules{HUEDevice}{defptr}{$code}) ) {
@ -1588,14 +1835,18 @@ HUEBridge_Autocreate($;$$)
HUEDeviceSetIcon($devname); HUEDeviceSetIcon($devname);
$defs{$devname}{helper}{fromAutocreate} = 1 ; $defs{$devname}{helper}{fromAutocreate} = 1 ;
$autocreated++; $created[1]++;
} }
} }
if( $sensors || $hash->{websocket} ) { if( $sensors || $hash->{websocket} || $hash->{has_v2_api} ) {
$result = HUEBridge_Call($hash,undef, 'sensors', undef); $result = HUEBridge_Call($hash,undef, 'sensors', undef);
foreach my $key ( keys %{$result} ) { foreach my $id ( sort {$a<=>$b} keys %{$result} ) {
my $id= $key; if( $result->{$id}{type} eq 'CLIPGenericStatus' ) {
Log3 $name, 4, "$name: ignoring sensor $id ($result->{$id}{name}) of type $result->{$id}{type} in autocreate";
$ignored[2]++;
next;
}
my $code = $name ."-S". $id; my $code = $name ."-S". $id;
if( defined($modules{HUEDevice}{defptr}{$code}) ) { if( defined($modules{HUEDevice}{defptr}{$code}) ) {
@ -1621,17 +1872,21 @@ HUEBridge_Autocreate($;$$)
HUEDeviceSetIcon($devname); HUEDeviceSetIcon($devname);
$defs{$devname}{helper}{fromAutocreate} = 1 ; $defs{$devname}{helper}{fromAutocreate} = 1 ;
$autocreated++; $created[2]++;
} }
} }
} }
if( $autocreated ) { sub sum { my $sum = 0; $sum += $_ for @_; return $sum }
Log3 $name, 2, "$name: autocreated $autocreated devices";
my $created = join( '/', @created );
my $ignored = join( '/', @ignored );
if( !$force || sum(@created) || sum(@ignored) ) {
Log3 $name, 2, "$name: autocreate: created $created devices (ignored $ignored)";
CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) ); CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
} }
return "created $autocreated devices"; return "created $created devices (ignored $ignored)";
} }
sub sub
@ -1879,6 +2134,7 @@ HUEBridge_HTTP_Call2($$$$;$)
return HUEBridge_ProcessResponse($hash, $json); return HUEBridge_ProcessResponse($hash, $json);
HUEBridge_dispatch( {hash=>$hash,chash=>$chash,type=>$path},$err,$data ); HUEBridge_dispatch( {hash=>$hash,chash=>$chash,type=>$path},$err,$data );
} else { } else {
Log3 $name, 4, "using HttpUtils_NonblockingGet: $method ". ($path?$path:''); Log3 $name, 4, "using HttpUtils_NonblockingGet: $method ". ($path?$path:'');
@ -1899,6 +2155,33 @@ HUEBridge_HTTP_Call2($$$$;$)
} }
} }
sub
HUEBridge_getv2resources($;$)
{
my ($hash,$blocking) = @_;
my $name = $hash->{NAME};
my $params = {
url => "https://$hash->{host}/clip/v2/resource",
method => 'GET',
timeout => 5,
header => { 'HUE-Application-Key' => $attr{$name}{key}, },
type => 'resource',
hash => $hash,
callback => \&HUEBridge_dispatch,
};
if( $blocking ) {
my($err,$data) = HttpUtils_BlockingGet( $params );
HUEBridge_dispatch($params, $err, $data );
} else {
HttpUtils_NonblockingGet( $params );
}
}
sub sub
HUEBridge_dispatch($$$;$) HUEBridge_dispatch($$$;$)
{ {
@ -1906,10 +2189,246 @@ HUEBridge_dispatch($$$;$)
my $hash = $param->{hash}; my $hash = $param->{hash};
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#Log3 $name, 5, "HUEBridge_dispatch"; Log3 $name, 4, "$name: dispatch";
Log3 $name, 5, "HUEBridge_dispatch". ($param->{type}?": $param->{type}":"");
my $type = $param->{type};
if( $err ) { if( $err ) {
Log3 $name, 2, "$name: http request failed: $err"; Log3 $name, 2, "$name: http request failed: $err";
if( $type eq 'event' ) {
if( defined($hash->{helper}{HTTP_CONNECTION}) && defined($hash->{helper}{HTTP_CONNECTION}{lastID}) ) {
$hash->{EventStream} = 'terminated';
Log3 $name, 2, "name: EventStream: $hash->{EventStream}";
HUEBridge_openEventStream( $hash );
} else {
$hash->{EventStream} = 'terminated; retrying later';
Log3 $name, 2, "name: EventStream: $hash->{EventStream}";
RemoveInternalTimer($hash, "HUEBridge_openEventStream" );
InternalTimer(gettimeofday()+2, "HUEBridge_openEventStream", $hash, 0);
}
}
return undef;
} elsif( defined($type) && $type eq 'resource' ) {
$json = eval { JSON->new->utf8(0)->decode($data) };
Log3 $name, 2, "$name: json error: $@ in $data" if( $@ );
delete $hash->{helper}{resource};
return undef if( !$json );
Log3 $name, 1, "$name: error: ". Dumper $json->{errors} if( scalar @{$json->{errors}} );
$hash->{helper}{resource} = $json;
Log3 $name, 5, "$name: received: ". Dumper $json;
foreach my $item (@{$hash->{helper}{resource}{data}}) {
$hash->{helper}{resource}{by_id}{$item->{id}} = $item;
}
return undef;
} elsif( defined($type) && $type eq 'event' ) {
$hash->{EventStream} = 'connected';
Log3 $name, 4, "name: EventStream: $hash->{EventStream}";
if( $hash->{INTERVAL} && $hash->{INTERVAL} < 60 ) {
$hash->{INTERVAL} = 60;
Log3 $name, 2, "$name: EventStream connected, changing interval to $hash->{INTERVAL}";
}
CommandDeleteAttr( undef, "$name pollDevices" ) if defined( AttrVal($name, 'pollDevices', undef) );
#Log3 $name, 5, "$name: EventStream: got: $data";
while($data =~ m/([^:]*):\s*(.+)(\r?\n)?(.*)/) {
my $key = $1;
my $value = $2;
$data = $4;
if( !$key ) {
Log3 $name, 5, "$name: ignoring: $value";
HUEBridge_getv2resources($hash);
next;
}
if( $key eq 'id' ) {
Log3 $name, 5, "$name: EventStream: got id: $value";
$hash->{helper}{HTTP_CONNECTION}{lastID} = $value;
} elsif( $key eq 'data' ) {
Log3 $name, 5, "$name: EventStream: got data: $value";
if( $value && $value !~ m/^[\[{].*[\]}]$/ ) {
Log3 $name, 2, "$name: invalid json detected: $value";
return undef;
}
$json = eval { JSON->new->utf8(0)->decode($value) };
Log3 $name, 2, "$name: json error: $@ in $value" if( $@ );
return undef if( !$json );
Log3 $name, 4, "$name: received: ". Dumper $json;
for my $event ( @{$json} ) {
my $changed = "";
if( $event->{type} eq 'update' ) {
Log3 $name, 4, "$name: got $event->{type} event";
for my $data ( @{$event->{data}} ) {
my(undef, $t, $id) = split( '/', $data->{id_v1} );
if( !defined($t) || !defined($id) ) {
Log3 $name, 3, "$name: ignoring event type $data->{type}";
next;
}
my $code;
$code = $name ."-". $id if( $t eq 'lights' );
$code = $name ."-S". $id if( $t eq 'sensors' );
$code = $name ."-G". $id if( $t eq 'groups' );
if( !$code ) {
Log3 $name, 3, "$name: ignoring event for $t";
next;
}
if( my $chash = $modules{HUEDevice}{defptr}{$code} ) {
my $creationtime = substr($event->{creationtime},0,19);
#substr( $creationtime, 10, 1, '_' );
#$creationtime = FmtDateTime( SVG_time_to_sec($creationtime) + $hash->{helper}{offsetUTC} ) if( defined($hash->{helper}{offsetUTC}) );
#substr( $creationtime, 10, 1, 'T' );
my $obj = { state => { lastupdated => $creationtime },
v2_id => $data->{owner}{rid},
v2_service => $data->{id} };
$obj->{v2_id} = $obj->{v2_service} if( $t eq 'groups' );
my $device = $hash->{helper}{resource}{by_id}{$obj->{v2_id}};
if( !$device ) {
Log3 $name, 2, "$name: event for unknown device received, tying to refresh resouces";
HUEBridge_getv2resources($hash, 1);
HUEBridge_Autocreate($hash);
$device = $hash->{helper}{resource}{by_id}{$obj->{v2_id}};
}
my $service = $hash->{helper}{resource}{by_id}{$obj->{v2_service}};
if( !$service ) {
Log3 $name, 2, "$name: event for unknown service received, tying to refresh resouces";
HUEBridge_getv2resources($hash, 1);
$service = $hash->{helper}{resource}{by_id}{$obj->{v2_service}};
}
#Log 1, Dumper $device;
#Log 1, Dumper $service;
if( $data->{type} eq 'motion' ) {
$obj->{state}{presence} = $data->{motion}{motion} if( defined($data->{motion}) );
} elsif( $data->{type} eq 'button' ) {
my $input = $service->{metadata}{control_id};
my $eventtype = $data->{button}{last_event};
#Log 1, "input: $input";
#Log 1, "eventtype: $eventtype";
my $buttonevent;
if( $input
&& defined($chash->{helper}{events})
&& defined($chash->{helper}{events}[$input-1])
&& defined($chash->{helper}{events}[$input-1]{$eventtype}) ) {
$buttonevent = $chash->{helper}{events}[$input-1]{$eventtype};
} elsif( $eventtype eq 'initial_press' ) {
$buttonevent = "${input}000";
} elsif( $eventtype eq 'repeat' ) {
$buttonevent = "${input}001";
} elsif( $eventtype eq 'short_release' ) {
$buttonevent = "${input}002";
} elsif( $eventtype eq 'long_release' ) {
$buttonevent = "${input}003";
}
#$obj->{state}{input} = $input;
#$obj->{state}{eventtype} = $eventtype;
$obj->{state}{buttonevent} = $buttonevent;
} elsif( $data->{type} eq 'temperature' ) {
$obj->{state}{temperature} = int($data->{temperature}{temperature}*100) if( defined($data->{temperature})
&& $data->{temperature}{temperature_valid} );
} elsif( $data->{type} eq 'light_level' ) {
$obj->{state}{lightlevel} = $data->{light}{light_level} if( defined($data->{light})
&& $data->{light}{light_level_valid} );
} elsif( $data->{type} eq 'zigbee_connectivity' ) {
$obj->{state}{reachable} = ($data->{status} eq 'connected') ? 1 : 0;
} else {
$obj->{state}{on} = $data->{on}{on} if( defined($data->{on}) );
$obj->{state}{bri} = int($data->{dimming}{brightness} * 254 / 100) if( defined($data->{dimming}) );
if( defined($data->{color}) ) {
if( my $xy = $data->{color}{xy} ) {
$obj->{state}{colormode} = 'xy';
$obj->{state}{xy} = [$xy->{x}, $xy->{y}];
}
}
if( defined($data->{color_temperature}) && defined($data->{color_temperature}{mirek}) ) {
$obj->{state}{colormode} = 'ct';
$obj->{state}{ct} = $data->{color_temperature}{mirek};
}
}
if( defined($data->{dynamics}) ) {
$obj->{state}{dynamics_speed} = $data->{dynamics}{speed} if( $data->{dynamics}{speed_valid} );
$obj->{state}{dynamics_status} = $data->{dynamics}{status};
}
Log3 $name, 4, "$name: created from event: ". Dumper $obj;
if( HUEDevice_Parse($chash, $obj) && !$chash->{helper}{devtype} ) {
$changed .= "," if( $changed );
$changed .= $chash->{ID};
}
} else {
Log3 $name, 3, "$name: message for unknown device received: $code";
}
}
} elsif( $event->{type} eq 'add' ) {
Log3 $name, 4, "$name: got $event->{type} event";
HUEBridge_getv2resources($hash, 1);
HUEBridge_Autocreate($hash);
} else {
Log3 $name, 3, "$name: unknown event type $event->{type}: $data";
}
HUEBridge_updateGroups($hash, $changed) if( $changed ); # not needed ?
}
} else {
Log3 $name, 4, "$name: EventStream: unknown event: $key: $value";
}
}
return undef;
} elsif( defined($type) && $type eq 'application id' ) {
if( $param->{httpheader} =~ m/hue-application-id:\s?([^\s;]*)/i ) {
$hash->{'application id'} = $1;
}
} elsif( $data || $json ) { } elsif( $data || $json ) {
if( !$data && !$json ) { if( !$data && !$json ) {
Log3 $name, 2, "$name: empty answer received"; Log3 $name, 2, "$name: empty answer received";
@ -1922,13 +2441,11 @@ HUEBridge_dispatch($$$;$)
my $queryAfterSet = AttrVal( $name,'queryAfterSet', 1 ); my $queryAfterSet = AttrVal( $name,'queryAfterSet', 1 );
if( !$json ) { if( !$json ) {
$json = eval { JSON->new->utf8(0)->decode($data) } if( !$json ); $json = eval { JSON->new->utf8(0)->decode($data) };
Log3 $name, 2, "$name: json error: $@ in $data" if( $@ ); Log3 $name, 2, "$name: json error: $@ in $data" if( $@ );
} }
return undef if( !$json ); return undef if( !$json );
my $type = $param->{type};
if( ref($json) eq 'ARRAY' ) { if( ref($json) eq 'ARRAY' ) {
HUEBridge_ProcessResponse($hash,$json) if( !$queryAfterSet ); HUEBridge_ProcessResponse($hash,$json) if( !$queryAfterSet );
@ -2195,6 +2712,8 @@ HUEBridge_Attr($$$)
1; 1;
__END__
=pod =pod
=item tag cloudfree =item tag cloudfree
=item tag publicAPI =item tag publicAPI
@ -2342,9 +2861,9 @@ HUEBridge_Attr($$$)
1 -> use HttpUtils_NonblockingGet<br> 1 -> use HttpUtils_NonblockingGet<br>
not set -> use old module specific implementation</li> not set -> use old module specific implementation</li>
<li>pollDevices<br> <li>pollDevices<br>
1 -> the bridge will poll all lights in one go instead of each device polling itself independently<br> 1 -> the bridge will poll all lights in one go instead of each light polling itself independently<br>
2 -> the bridge will poll all devices in one go instead of each device polling itself independently<br> 2 -> the bridge will poll all devices in one go instead of each device polling itself independently<br>
default is 1.</li> default is 2. will be deleted if v2 api is detected and eventstream connects.</li>
<li>createGroupReadings<br> <li>createGroupReadings<br>
create 'artificial' readings for group devices.</li> create 'artificial' readings for group devices.</li>
0 -> create readings only for group devices where createGroupReadings ist set to 1 0 -> create readings only for group devices where createGroupReadings ist set to 1

View File

@ -492,8 +492,10 @@ HUEDevice_Define($$) {
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
if( $init_done ) { if( $init_done ) {
HUEDevice_GetUpdate($hash); HUEDevice_GetUpdate($hash);
} else { } else {
InternalTimer(gettimeofday()+10, "HUEDevice_GetUpdate", $hash, 0); InternalTimer(gettimeofday()+10, "HUEDevice_GetUpdate", $hash, 0);
} }
return undef; return undef;
@ -1457,6 +1459,27 @@ HUEDevice_Parse($$)
my($hash,$result) = @_; my($hash,$result) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if( !defined($hash->{has_v2_api}) && defined($hash->{IODev}) ) {
$hash->{has_v2_api} = $hash->{IODev}{has_v2_api} if( defined($hash->{IODev}{has_v2_api}) );
Log3 $name, 4, "$name: bridge has v2 api: $hash->{has_v2_api}";
if( $hash->{INTERVAL} && $hash->{has_v2_api} ) {
if( defined($hash->{IODev}{EventStream}) && $hash->{IODev}{EventStream} eq 'connected' ) {
delete $hash->{INTERVAL};
Log3 $name, 2, "$name: bridge has v2 api, EventStream connected, removing interval";
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEDevice_GetUpdate", $hash, 0) if( $hash->{INTERVAL} );
} else {
delete $hash->{has_v2_api};
Log3 $name, 2, "$name: bridge has v2 api, EventStream not jet connected";
}
}
}
if( ref($result) ne "HASH" ) { if( ref($result) ne "HASH" ) {
if( ref($result) && $HUEDevice_hasDataDumper) { if( ref($result) && $HUEDevice_hasDataDumper) {
#Log3 $name, 2, "$name: got wrong status message for $name: ". Dumper $result; #Log3 $name, 2, "$name: got wrong status message for $name: ". Dumper $result;