mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-25 15:59:21 +00:00
30_HUEBridge.pm, 31_HUEDevice.pm: added deCONZ WebSocket support. see forum: https://forum.fhem.de/index.php/topic,80985.msg730410.html#msg730410
git-svn-id: https://svn.fhem.de/fhem/trunk@15718 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
266ff46cc6
commit
52dc2a3cbe
@ -15,13 +15,15 @@ use Data::Dumper;
|
||||
|
||||
use HttpUtils;
|
||||
|
||||
use IO::Socket::INET;
|
||||
|
||||
sub HUEBridge_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{ReadFn} = "HUEBridge_Read";
|
||||
$hash->{WriteFn} = "HUEBridge_Read";
|
||||
$hash->{WriteFn} = "HUEBridge_Write";
|
||||
$hash->{Clients} = ":HUEDevice:";
|
||||
|
||||
#Consumer
|
||||
@ -35,7 +37,122 @@ sub HUEBridge_Initialize($)
|
||||
}
|
||||
|
||||
sub
|
||||
HUEBridge_Read($@)
|
||||
HUEBridge_Read($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $buf;
|
||||
my $len = sysread($hash->{CD}, $buf, 10240);
|
||||
my $peerhost = $hash->{CD}->peerhost;
|
||||
my $peerport = $hash->{CD}->peerport;
|
||||
|
||||
my $close = 0;
|
||||
if( !defined($len) || !$len ) {
|
||||
$close = 1;
|
||||
|
||||
} elsif( $hash->{websocket} ) {
|
||||
$hash->{buf} .= $buf;
|
||||
|
||||
do {
|
||||
my $fin = (ord(substr($hash->{buf},0,1)) & 0x80)?1:0;
|
||||
my $op = (ord(substr($hash->{buf},0,1)) & 0x0F);
|
||||
my $mask = (ord(substr($hash->{buf},1,1)) & 0x80)?1:0;
|
||||
my $len = (ord(substr($hash->{buf},1,1)) & 0x7F);
|
||||
my $i = 2;
|
||||
if( $len == 126 ) {
|
||||
$len = unpack( 'n', substr($hash->{buf},$i,2) );
|
||||
$i += 2;
|
||||
} elsif( $len == 127 ) {
|
||||
$len = unpack( 'q', substr($hash->{buf},$i,8) );
|
||||
$i += 8;
|
||||
}
|
||||
if( $mask ) {
|
||||
$mask = substr($hash->{buf},$i,4);
|
||||
$i += 4;
|
||||
}
|
||||
#FIXME: hande !$fin
|
||||
return if( $len > length($hash->{buf})-$i );
|
||||
|
||||
my $data = substr($hash->{buf}, $i, $len);
|
||||
$hash->{buf} = substr($hash->{buf},$i+$len);
|
||||
#Log 1, ">>>$data<<<";
|
||||
|
||||
if( $data eq '?' ) {
|
||||
#ignore keepalive
|
||||
|
||||
} elsif( $op == 0x01 ) {
|
||||
my $obj = eval { decode_json($data) };
|
||||
|
||||
if( $obj ) {
|
||||
Log3 $name, 5, "$name: websocket data: ". Dumper $obj;
|
||||
} else {
|
||||
Log3 $name, 2, "$name: unhandled websocket text $data";
|
||||
|
||||
}
|
||||
|
||||
if( $obj->{t} eq 'event' && $obj->{e} eq 'changed' ) {
|
||||
my $code;
|
||||
my $id = $obj->{id};
|
||||
$code = $name ."-". $id if( $obj->{r} eq 'lights' );
|
||||
$code = $name ."-S". $id if( $obj->{r} eq 'sensors' );
|
||||
$code = $name ."-G". $id if( $obj->{r} eq 'groups' );
|
||||
if( !$code ) {
|
||||
Log3 $name, 5, "$name: ignoring event: $code";
|
||||
return;
|
||||
}
|
||||
|
||||
my $chash = $modules{HUEDevice}{defptr}{$code};
|
||||
if( defined($chash) ) {
|
||||
HUEDevice_Parse($chash,$obj);
|
||||
} else {
|
||||
Log3 $name, 4, "$name: message for unknow device received: $code";
|
||||
}
|
||||
|
||||
} elsif( $obj->{t} eq 'event' && $obj->{e} eq 'scene-called' ) {
|
||||
Log3 $name, 5, "$name: todo: handle websocket scene-called $data";
|
||||
# trigger scene event ?
|
||||
|
||||
} elsif( $obj->{t} eq 'event' && $obj->{e} eq 'added' ) {
|
||||
Log3 $name, 5, "$name: websocket add: $data";
|
||||
HUEBridge_Autocreate($hash);
|
||||
|
||||
} elsif( $obj->{t} eq 'event' && $obj->{e} eq 'deleted' ) {
|
||||
Log3 $name, 5, "$name: todo: handle websocket delete $data";
|
||||
# do what ?
|
||||
|
||||
} else {
|
||||
Log3 $name, 5, "$name: unknown websocket data: $data";
|
||||
}
|
||||
|
||||
} else {
|
||||
Log3 $name, 2, "$name: unhandled websocket data: $data";
|
||||
|
||||
}
|
||||
} while( $hash->{buf} && !$close );
|
||||
|
||||
} elsif( $buf =~ m'^HTTP/1.1 101 Switching Protocols'i ) {
|
||||
$hash->{websocket} = 1;
|
||||
#my $buf = plex_msg2hash($buf, 1);
|
||||
Log 1, $buf;
|
||||
|
||||
Log3 $name, 3, "$name: websocket: Switching Protocols ok";
|
||||
|
||||
} else {
|
||||
Log 1, $buf;
|
||||
$close = 1;
|
||||
Log3 $name, 2, "$name: websocket: Switching Protocols failed";
|
||||
}
|
||||
|
||||
if( $close ) {
|
||||
HUEBridge_closeWebsocket($hash);
|
||||
|
||||
Log3 $name, 2, "$name: websocket closed";
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
HUEBridge_Write($@)
|
||||
{
|
||||
my ($hash,$chash,$name,$id,$obj)= @_;
|
||||
|
||||
@ -80,7 +197,7 @@ HUEBridge_Detect($)
|
||||
}
|
||||
|
||||
Log3 $name, 3, "HUEBridge_Detect: ${host}";
|
||||
$hash->{Host} = $host;
|
||||
$hash->{host} = $host;
|
||||
|
||||
return $host;
|
||||
}
|
||||
@ -108,7 +225,7 @@ HUEBridge_Define($$)
|
||||
|
||||
readingsSingleUpdate($hash, 'state', 'initialized', 1 );
|
||||
|
||||
$hash->{Host} = $host;
|
||||
$hash->{host} = $host;
|
||||
$hash->{INTERVAL} = $interval;
|
||||
|
||||
$attr{$name}{"key"} = join "",map { unpack "H*", chr(rand(256)) } 1..16 unless defined( AttrVal($name, "key", undef) );
|
||||
@ -156,6 +273,79 @@ sub HUEBridge_Undefine($$)
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
HUEBridge_hash2header($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
return $hash if( ref($hash) ne 'HASH' );
|
||||
|
||||
my $header;
|
||||
foreach my $key (keys %{$hash}) {
|
||||
#$header .= "\r\n" if( $header );
|
||||
$header .= "$key: $hash->{$key}\r\n";
|
||||
}
|
||||
|
||||
return $header;
|
||||
}
|
||||
sub HUEBridge_closeWebsocket($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
delete $hash->{buf};
|
||||
delete $hash->{websocket};
|
||||
|
||||
close($hash->{CD}) if( defined($hash->{CD}) );
|
||||
delete($hash->{CD});
|
||||
|
||||
delete($selectlist{$name});
|
||||
delete($hash->{FD});
|
||||
|
||||
delete($hash->{PORT});
|
||||
}
|
||||
sub HUEBridge_openWebsocket($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return if( !defined($hash->{websocketport}) );
|
||||
|
||||
HUEBridge_closeWebsocket($hash);
|
||||
|
||||
my ($host,undef) = split(':',$hash->{host},2);
|
||||
if( my $socket = IO::Socket::INET->new(PeerAddr=>"$host:$hash->{websocketport}", Timeout=>2, Blocking=>1, ReuseAddr=>1) ) {
|
||||
$hash->{CD} = $socket;
|
||||
$hash->{FD} = $socket->fileno();
|
||||
|
||||
$hash->{PORT} = $socket->sockport if( $socket->sockport );
|
||||
|
||||
$selectlist{$name} = $hash;
|
||||
|
||||
Log3 $name, 3, "$name: websocket opened to $host:$hash->{websocketport}";
|
||||
|
||||
|
||||
my $ret = "GET ws://$host:$hash->{websocketport} HTTP/1.1\r\n";
|
||||
$ret .= HUEBridge_hash2header( { 'Host' => "$host:$hash->{websocketport}",
|
||||
'Upgrade' => 'websocket',
|
||||
'Connection' => 'Upgrade',
|
||||
'Pragma' => 'no-cache',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Sec-WebSocket-Key' => 'RkhFTQ==',
|
||||
'Sec-WebSocket-Version' => '13',
|
||||
} );
|
||||
|
||||
$ret .= "\r\n";
|
||||
#Log 1, $ret;
|
||||
|
||||
syswrite($hash->{CD}, $ret );
|
||||
|
||||
} else {
|
||||
Log3 $name, 2, "$name: failed to open websocket";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sub HUEBridge_fillBridgeInfo($$)
|
||||
{
|
||||
my ($hash,$config) = @_;
|
||||
@ -166,6 +356,11 @@ sub HUEBridge_fillBridgeInfo($$)
|
||||
$hash->{swversion} = $config->{swversion};
|
||||
$hash->{apiversion} = $config->{apiversion};
|
||||
|
||||
if( defined($config->{websocketport}) ) {
|
||||
$hash->{websocketport} = $config->{websocketport};
|
||||
HUEBridge_openWebsocket($hash);
|
||||
}
|
||||
|
||||
if( $hash->{apiversion} ) {
|
||||
my @l = split( '\.', $config->{apiversion} );
|
||||
$hash->{helper}{apiversion} = ($l[0] << 16) + ($l[1] << 8) + $l[2];
|
||||
@ -189,7 +384,7 @@ HUEBridge_OpenDev($)
|
||||
HUEBridge_Detect($hash) if( defined($hash->{NUPNP}) );
|
||||
|
||||
my ($err,$ret) = HttpUtils_BlockingGet({
|
||||
url => "http://$hash->{Host}/description.xml",
|
||||
url => "http://$hash->{host}/description.xml",
|
||||
method => "GET",
|
||||
timeout => 3,
|
||||
});
|
||||
@ -775,6 +970,10 @@ HUEBridge_GetUpdate($)
|
||||
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEBridge_GetUpdate", $hash, 0);
|
||||
}
|
||||
|
||||
if( $hash->{websocketport} && !$hash->{PORT} ) {
|
||||
HUEBridge_openWebsocket($hash);
|
||||
}
|
||||
|
||||
my $type;
|
||||
my $result;
|
||||
my $poll_devices = AttrVal($name, "pollDevices", 1);
|
||||
@ -1079,7 +1278,7 @@ HUEBridge_HTTP_Call($$$;$)
|
||||
|
||||
#return { state => {reachable => 0 } } if($attr{$name} && $attr{$name}{disable});
|
||||
|
||||
my $uri = "http://" . $hash->{Host} . "/api";
|
||||
my $uri = "http://" . $hash->{host} . "/api";
|
||||
if( defined($obj) ) {
|
||||
$method = 'PUT' if( !$method );
|
||||
|
||||
@ -1133,7 +1332,7 @@ HUEBridge_HTTP_Call2($$$$;$)
|
||||
|
||||
#return { state => {reachable => 0 } } if($attr{$name} && $attr{$name}{disable});
|
||||
|
||||
my $url = "http://" . $hash->{Host} . "/api";
|
||||
my $url = "http://" . $hash->{host} . "/api";
|
||||
my $blocking = $attr{$name}{httpUtils} < 1;
|
||||
$blocking = 1 if( !defined($chash) );
|
||||
if( defined($obj) ) {
|
||||
@ -1603,14 +1802,14 @@ HUEBridge_Attr($$$)
|
||||
available (indicated by updatestate with a value of 2. The version and release date is shown in the reading swupdate.<br>
|
||||
A notify of the form <code>define HUEUpdate notify bridge:swupdate.* {...}</code>
|
||||
can be used to be informed about available firmware updates.<br></li>
|
||||
<li>inactive<br>
|
||||
inactivates the current device. note the slight difference to the
|
||||
<li>inactive<br>
|
||||
inactivates the current device. note the slight difference to the
|
||||
disable attribute: using set inactive the state is automatically saved
|
||||
to the statefile on shutdown, there is no explicit save necesary.<br>
|
||||
this command is intended to be used by scripts to temporarily
|
||||
deactivate the harmony device.<br>
|
||||
deactivate the harmony device.<br>
|
||||
the concurrent setting of the disable attribute is not recommended.</li>
|
||||
<li>active<br>
|
||||
<li>active<br>
|
||||
activates the current device (see inactive).</li>
|
||||
</ul><br>
|
||||
|
||||
|
@ -996,15 +996,15 @@ HUEDevice_ReadFromServer($@)
|
||||
if(!$iohash ||
|
||||
!$iohash->{TYPE} ||
|
||||
!$modules{$iohash->{TYPE}} ||
|
||||
!$modules{$iohash->{TYPE}}{ReadFn}) {
|
||||
Log3 $name, 5, "No I/O device or ReadFn found for $name";
|
||||
!$modules{$iohash->{TYPE}}{WriteFn}) {
|
||||
Log3 $name, 5, "No I/O device or WriteFn found for $name";
|
||||
return;
|
||||
}
|
||||
|
||||
no strict "refs";
|
||||
#my $ret;
|
||||
unshift(@a,$name);
|
||||
$ret = &{$modules{$iohash->{TYPE}}{ReadFn}}($iohash, @a);
|
||||
$ret = &{$modules{$iohash->{TYPE}}{WriteFn}}($iohash, @a);
|
||||
use strict "refs";
|
||||
return $ret;
|
||||
}
|
||||
@ -1249,6 +1249,7 @@ HUEDevice_Parse($$)
|
||||
$readings{humidity} = $state->{humidity} * 0.01 if( defined($state->{humidity}) );
|
||||
$readings{daylight} = $state->{daylight}?'1':'0' if( defined($state->{daylight}) );
|
||||
$readings{temperature} = $state->{temperature} * 0.01 if( defined($state->{temperature}) );
|
||||
$readings{pressure} = $state->{pressure} if( defined($state->{pressure}) );
|
||||
}
|
||||
|
||||
if( scalar keys %readings ) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user