added modules 30_HUEDevice and 31_HUEBridge for phillips hue and smartlink devices
git-svn-id: https://fhem.svn.sourceforge.net/svnroot/fhem/trunk/fhem@2762 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
2
CHANGED
2
CHANGED
@@ -1,4 +1,6 @@
|
|||||||
- SVN
|
- SVN
|
||||||
|
- feature: new Modules 30_HUEDevice and 31_HUEBridge for phillips hue and
|
||||||
|
smartlink devices (by justme1968)
|
||||||
- change: SYSSTAT: allow remote monitoring by ssh
|
- change: SYSSTAT: allow remote monitoring by ssh
|
||||||
- change: SYSSTAT: allow less frequent updates for diskusage
|
- change: SYSSTAT: allow less frequent updates for diskusage
|
||||||
- feature: new Module 32_SYSSTAT to monitor system load and disk usage
|
- feature: new Module 32_SYSSTAT to monitor system load and disk usage
|
||||||
|
|||||||
434
FHEM/30_HUEBridge.pm
Normal file
434
FHEM/30_HUEBridge.pm
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
|
||||||
|
package main;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use POSIX;
|
||||||
|
use JSON;
|
||||||
|
use Try::Tiny;
|
||||||
|
use Data::Dumper;
|
||||||
|
use MIME::Base64;
|
||||||
|
|
||||||
|
sub HUEBridge_Initialize($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
# Provider
|
||||||
|
$hash->{ReadFn} = "HUEBridge_Read";
|
||||||
|
$hash->{WriteFn} = "HUEBridge_Read";
|
||||||
|
$hash->{Clients} = ":HUEDevice:";
|
||||||
|
|
||||||
|
#Consumer
|
||||||
|
$hash->{DefFn} = "HUEBridge_Define";
|
||||||
|
$hash->{SetFn} = "HUEBridge_Set";
|
||||||
|
$hash->{UndefFn} = "HUEBridge_Undefine";
|
||||||
|
$hash->{AttrList}= "key loglevel:0,1,2,3,4,5";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub
|
||||||
|
HUEBridge_Read($@)
|
||||||
|
{
|
||||||
|
my ($hash,$name,$id,$obj)= @_;
|
||||||
|
|
||||||
|
return HUEBridge_Call($hash, 'lights/' . $id, $obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub HUEBridge_Define($$)
|
||||||
|
{
|
||||||
|
my ($hash, $def) = @_;
|
||||||
|
|
||||||
|
my @args = split("[ \t]+", $def);
|
||||||
|
|
||||||
|
return "Usage: define <name> HUEBridge <host> [interval]" if(@args < 3);
|
||||||
|
|
||||||
|
my ($name, $type, $host, $interval) = @args;
|
||||||
|
|
||||||
|
$interval= 300 unless defined($interval);
|
||||||
|
if( $interval < 60 ) { $interval = 60; }
|
||||||
|
|
||||||
|
$hash->{STATE} = 'Initialized';
|
||||||
|
|
||||||
|
$hash->{Host} = $host;
|
||||||
|
$hash->{INTERVAL} = $interval;
|
||||||
|
|
||||||
|
$attr{$name}{"key"} = join "",map { unpack "H*", chr(rand(256)) } 1..16 unless defined( AttrVal($name, "key", undef) );
|
||||||
|
|
||||||
|
#HUEBridge_OpenDev($hash);
|
||||||
|
InternalTimer(gettimeofday()+10, "HUEBridge_OpenDev", $hash, 0);
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub HUEBridge_Undefine($$)
|
||||||
|
{
|
||||||
|
my ($hash,$arg) = @_;
|
||||||
|
|
||||||
|
RemoveInternalTimer($hash);
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub HUEBridge_OpenDev($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
my $result = HUEBridge_Call($hash, 'config', undef);
|
||||||
|
if( !defined($result) ) {
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash->{name} = $result->{'name'};
|
||||||
|
$hash->{swversion} = $result->{'swversion'};
|
||||||
|
|
||||||
|
if( !defined($result->{'mac'}) )
|
||||||
|
{
|
||||||
|
HUEBridge_Pair($hash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash->{mac} = $result->{'mac'};
|
||||||
|
|
||||||
|
$hash->{STATE} = 'Connected';
|
||||||
|
HUEBridge_GetUpdate($hash);
|
||||||
|
|
||||||
|
HUEBridge_Autocreate($hash);
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
sub HUEBridge_Pair($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
$hash->{STATE} = 'Pairing';
|
||||||
|
|
||||||
|
my $result = HUEBridge_Register($hash);
|
||||||
|
if( $result->{'error'} )
|
||||||
|
{
|
||||||
|
RemoveInternalTimer($hash);
|
||||||
|
InternalTimer(gettimeofday()+5, "HUEBridge_Pair", $hash, 0);
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash->{STATE} = 'Paired';
|
||||||
|
|
||||||
|
HUEBridge_OpenDev($hash);
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub
|
||||||
|
HUEBridge_Set($@)
|
||||||
|
{
|
||||||
|
my ($hash, $name, $cmd) = @_;
|
||||||
|
|
||||||
|
# usage check
|
||||||
|
if($cmd eq 'statusRequest') {
|
||||||
|
RemoveInternalTimer($hash);
|
||||||
|
HUEBridge_GetUpdate($hash);
|
||||||
|
return undef;
|
||||||
|
} else {
|
||||||
|
return "Unknown argument $cmd, choose one of statusRequest";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub
|
||||||
|
HUEBridge_GetUpdate($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
if(!$hash->{LOCAL}) {
|
||||||
|
RemoveInternalTimer($hash);
|
||||||
|
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEBridge_GetUpdate", $hash, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $text='';
|
||||||
|
|
||||||
|
return($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub
|
||||||
|
HUEBridge_Autocreate($)
|
||||||
|
{
|
||||||
|
my ($hash)= @_;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
foreach my $d (keys %defs) {
|
||||||
|
next if($defs{$d}{TYPE} ne "autocreate");
|
||||||
|
return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
|
||||||
|
}
|
||||||
|
|
||||||
|
my $result = HUEBridge_Call($hash, 'lights', undef);
|
||||||
|
|
||||||
|
my @defined = ();
|
||||||
|
foreach my $d (keys %defs) {
|
||||||
|
next if($defs{$d}{TYPE} ne "HUEDevice");
|
||||||
|
if(defined($defs{$d}{fhem}) && defined($defs{$d}{fhem}{id})) {
|
||||||
|
push(@defined,$defs{$d}{fhem}{id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $key ( keys %$result )
|
||||||
|
{
|
||||||
|
my $id= $key;
|
||||||
|
|
||||||
|
my $found = 0;
|
||||||
|
foreach my $d (keys %defs) {
|
||||||
|
next if($defs{$d}{TYPE} ne "HUEDevice");
|
||||||
|
if(defined($defs{$d}{fhem}) &&
|
||||||
|
defined($defs{$d}{fhem}{id}) && $defs{$d}{fhem}{id} eq $id) {
|
||||||
|
Log 5, "$name id '$id' already defined as '$defs{$d}{NAME}'";
|
||||||
|
$found = 1;
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !$found ) {
|
||||||
|
my $devname= "HUEDevice" . $id;
|
||||||
|
my $define= "$devname HUEDevice $id";
|
||||||
|
|
||||||
|
Log 5, "$name create new device '$devname' for address '$id'";
|
||||||
|
|
||||||
|
my $cmdret= CommandDefine(undef,$define);
|
||||||
|
if($cmdret) {
|
||||||
|
Log 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
|
||||||
|
} else {
|
||||||
|
$cmdret= CommandAttr(undef,"$devname alias ".$result->{$id}{name});
|
||||||
|
$cmdret= CommandAttr(undef,"$devname room HUEDevice");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub HUEBridge_ProcessResponse($$)
|
||||||
|
{
|
||||||
|
my ($hash,$obj) = @_;
|
||||||
|
|
||||||
|
#Log 3, ref($obj);
|
||||||
|
#Log 3, "Receiving: " . Dumper $obj;
|
||||||
|
|
||||||
|
if( ref($obj) eq 'ARRAY' )
|
||||||
|
{
|
||||||
|
if( defined($obj->[0]->{error}))
|
||||||
|
{
|
||||||
|
my $error = $obj->[0]->{error}->{'description'};
|
||||||
|
|
||||||
|
$hash->{STATE} = $error;
|
||||||
|
|
||||||
|
Log 3, $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($obj->[0]);
|
||||||
|
}
|
||||||
|
elsif( ref($obj) eq 'HASH' )
|
||||||
|
{
|
||||||
|
return $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub HUEBridge_Register($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
my $obj = {
|
||||||
|
'username' => AttrVal($hash->{NAME}, "key", ""),
|
||||||
|
'devicetype' => 'fhem',
|
||||||
|
};
|
||||||
|
|
||||||
|
return HUEBridge_Call($hash, undef, $obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
#Executes a JSON RPC
|
||||||
|
sub
|
||||||
|
HUEBridge_Call($$$)
|
||||||
|
{
|
||||||
|
my ($hash,$path,$obj) = @_;
|
||||||
|
|
||||||
|
#Log 3, "Sending: " . Dumper $obj;
|
||||||
|
|
||||||
|
my $json = undef;
|
||||||
|
$json = encode_json($obj) if $obj;
|
||||||
|
|
||||||
|
return HUEBridge_HTTP_Call($hash,$path,$json);
|
||||||
|
}
|
||||||
|
|
||||||
|
#JSON RPC over HTTP
|
||||||
|
sub HUEBridge_HTTP_Call($$$)
|
||||||
|
{
|
||||||
|
my ($hash,$path,$obj) = @_;
|
||||||
|
my $uri = "http://" . $hash->{Host} . "/api";
|
||||||
|
my $method = 'GET';
|
||||||
|
if( defined($obj) ) {
|
||||||
|
$method = 'PUT';
|
||||||
|
|
||||||
|
if( $hash->{STATE} eq 'Pairing' ) {
|
||||||
|
$method = 'POST';
|
||||||
|
} else {
|
||||||
|
$uri .= "/" . AttrVal($hash->{NAME}, "key", "");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$uri .= "/" . AttrVal($hash->{NAME}, "key", "");
|
||||||
|
}
|
||||||
|
if( defined $path) {
|
||||||
|
$uri .= "/" . $path;
|
||||||
|
}
|
||||||
|
#Log 3, "Url: " . $uri;
|
||||||
|
my $ret = HUEBridge_HTTP_Request(0,$uri,$method,undef,$obj,undef);
|
||||||
|
#Log 3, Dumper $ret;
|
||||||
|
if( !defined($ret) ) {
|
||||||
|
return undef;
|
||||||
|
} elsif($ret eq '') {
|
||||||
|
return undef;
|
||||||
|
} elsif($ret =~ /^error:(\d){3}$/) {
|
||||||
|
return "HTTP Error Code " . $1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# try {
|
||||||
|
# decode_json($ret);
|
||||||
|
# } catch {
|
||||||
|
# return undef;
|
||||||
|
# }
|
||||||
|
|
||||||
|
return HUEBridge_ProcessResponse($hash,decode_json($ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
#adapted version of the CustomGetFileFromURL subroutine from HttpUtils.pm
|
||||||
|
sub
|
||||||
|
HUEBridge_HTTP_Request($$$@)
|
||||||
|
{
|
||||||
|
my ($quiet, $url, $method, $timeout, $data, $noshutdown) = @_;
|
||||||
|
$timeout = 4.0 if(!defined($timeout));
|
||||||
|
|
||||||
|
my $displayurl= $quiet ? "<hidden>" : $url;
|
||||||
|
if($url !~ /^(http|https):\/\/([^:\/]+)(:\d+)?(\/.*)$/) {
|
||||||
|
Log 1, "HUEBridge_HTTP_Request $displayurl: malformed or unsupported URL";
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
my ($protocol,$host,$port,$path)= ($1,$2,$3,$4);
|
||||||
|
|
||||||
|
if(defined($port)) {
|
||||||
|
$port =~ s/^://;
|
||||||
|
} else {
|
||||||
|
$port = ($protocol eq "https" ? 443: 80);
|
||||||
|
}
|
||||||
|
$path= '/' unless defined($path);
|
||||||
|
|
||||||
|
|
||||||
|
my $conn;
|
||||||
|
if($protocol eq "https") {
|
||||||
|
eval "use IO::Socket::SSL";
|
||||||
|
if($@) {
|
||||||
|
Log 1, $@;
|
||||||
|
} else {
|
||||||
|
$conn = IO::Socket::SSL->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$conn = IO::Socket::INET->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
|
||||||
|
}
|
||||||
|
if(!$conn) {
|
||||||
|
Log 1, "HUEBridge_HTTP_Request $displayurl: Can't connect to $protocol://$host:$port\n";
|
||||||
|
undef $conn;
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host =~ s/:.*//;
|
||||||
|
#my $hdr = ($data ? "POST" : "GET")." $path HTTP/1.0\r\nHost: $host\r\n";
|
||||||
|
my $hdr = $method." $path HTTP/1.0\r\nHost: $host\r\n";
|
||||||
|
if(defined($data)) {
|
||||||
|
$hdr .= "Content-Length: ".length($data)."\r\n";
|
||||||
|
$hdr .= "Content-Type: application/json";
|
||||||
|
}
|
||||||
|
$hdr .= "\r\n\r\n";
|
||||||
|
syswrite $conn, $hdr;
|
||||||
|
syswrite $conn, $data if(defined($data));
|
||||||
|
shutdown $conn, 1 if(!$noshutdown);
|
||||||
|
|
||||||
|
my ($buf, $ret) = ("", "");
|
||||||
|
$conn->timeout($timeout);
|
||||||
|
for(;;) {
|
||||||
|
my ($rout, $rin) = ('', '');
|
||||||
|
vec($rin, $conn->fileno(), 1) = 1;
|
||||||
|
my $nfound = select($rout=$rin, undef, undef, $timeout);
|
||||||
|
if($nfound <= 0) {
|
||||||
|
Log 1, "HUEBridge_HTTP_Request $displayurl: Select timeout/error: $!";
|
||||||
|
undef $conn;
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $len = sysread($conn,$buf,65536);
|
||||||
|
last if(!defined($len) || $len <= 0);
|
||||||
|
$ret .= $buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret=~ s/(.*?)\r\n\r\n//s; # Not greedy: switch off the header.
|
||||||
|
my @header= split("\r\n", $1);
|
||||||
|
my $hostpath= $quiet ? "<hidden>" : $host . $path;
|
||||||
|
Log 4, "HUEBridge_HTTP_Request $displayurl: Got data, length: ".length($ret);
|
||||||
|
if(!length($ret)) {
|
||||||
|
Log 4, "HUEBridge_HTTP_Request $displayurl: Zero length data, header follows...";
|
||||||
|
for (@header) {
|
||||||
|
Log 4, "HUEBridge_HTTP_Request $displayurl: $_";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
undef $conn;
|
||||||
|
if($header[0] =~ /^[^ ]+ ([\d]{3})/ && $1 != 200) {
|
||||||
|
return "error:" . $1;
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
=pod
|
||||||
|
=begin html
|
||||||
|
|
||||||
|
<a name="HUEBridge"></a>
|
||||||
|
<h3>HUEBridge</h3>
|
||||||
|
<ul>
|
||||||
|
Module to access the bridge of the phillips hue lighting system.<br><br>
|
||||||
|
|
||||||
|
The actual hue bulbs, living colors or living whites devices are defined as <a href="#HUEDevice">HUEDevice</a> devices.
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
All newly found devices are autocreated at startup and added to the room HUEDevice.
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
Notes:
|
||||||
|
<ul>
|
||||||
|
<li>This module needs <code>JSON</code>.<br>
|
||||||
|
Pleease install with '<code>cpan install JSON</code>' or your method of choice.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
<a name="HUEBridge_Define_Define"></a>
|
||||||
|
<b>Define</b>
|
||||||
|
<ul>
|
||||||
|
<code>define <name> HUEBridge <host> [<interval>]</code><br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Defines a HUEBridge device with address <host>.<br><br>
|
||||||
|
|
||||||
|
The bridge status will be updated every <interval> seconds. The default and minimum is 60.<br><br>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
<ul>
|
||||||
|
<code>define bridge HUEBridge 10.0.1.1</code><br>
|
||||||
|
</ul>
|
||||||
|
</ul><br>
|
||||||
|
|
||||||
|
<a name="HUEBridge_Set"></a>
|
||||||
|
<b>Set</b>
|
||||||
|
<ul>
|
||||||
|
<li>statusRequest<br>
|
||||||
|
Update bridge status.</li>
|
||||||
|
</ul><br>
|
||||||
|
</ul><br>
|
||||||
|
|
||||||
|
=end html
|
||||||
|
=cut
|
||||||
460
FHEM/31_HUEDevice.pm
Normal file
460
FHEM/31_HUEDevice.pm
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
|
||||||
|
package main;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use POSIX;
|
||||||
|
use JSON;
|
||||||
|
use SetExtensions;
|
||||||
|
|
||||||
|
#Hue XY to RGB
|
||||||
|
#Z = 1 - X - Y
|
||||||
|
#
|
||||||
|
#|R| | X | | 3.2333 -1.5262 0.2791 |
|
||||||
|
#|G| = | Y | * |-0.8268 2.4667 0.3323 |
|
||||||
|
#|B| | Z | | 0.1294 0.1983 2.0280 |
|
||||||
|
#
|
||||||
|
#Example:
|
||||||
|
#XY == [0.4912, 0.4058]
|
||||||
|
#Z == 1 - 0.4912 - 0.4058 == 0.103
|
||||||
|
#
|
||||||
|
#R/255 = (0.4912 * 3.2333) + (0.4058 * -1.5262) + (0.103 * 0.2791) = 0.9976
|
||||||
|
#G/255 = (0.4912 * -0.8268) + (0.4058 * 2.4667) + (0.103 * 0.3323) = 0.6291
|
||||||
|
#B/255 = (0.4912 * 0.1294) + (0.4058 * 0.1983) + (0.103 * 2.0280) = 0.3529
|
||||||
|
|
||||||
|
my %models = (
|
||||||
|
LCT001 => 'HUE Bulb',
|
||||||
|
LLC001 => 'LivingColors G2',
|
||||||
|
LLC006 => 'LivingColors Iris',
|
||||||
|
LLC007 => 'LivingColors Bloom',
|
||||||
|
LWB001 => 'LivingWhites Bulb',
|
||||||
|
LWL001 => 'LivingWhites Outlet',
|
||||||
|
);
|
||||||
|
|
||||||
|
sub HUEDevice_Initialize($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
|
||||||
|
# Provide
|
||||||
|
|
||||||
|
#Consumer
|
||||||
|
$hash->{DefFn} = "HUEDevice_Define";
|
||||||
|
$hash->{SetFn} = "HUEDevice_Set";
|
||||||
|
$hash->{UndefFn} = "HUEDevice_Undefine";
|
||||||
|
$hash->{AttrList} = "IODev ".
|
||||||
|
"$readingFnAttributes ".
|
||||||
|
"model:".join(",", sort keys %models)." ".
|
||||||
|
"subType:dimmer,switch";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub HUEDevice_Define($$)
|
||||||
|
{
|
||||||
|
my ($hash, $def) = @_;
|
||||||
|
|
||||||
|
my @args = split("[ \t]+", $def);
|
||||||
|
|
||||||
|
return "Usage: define <name> HUEDevice <id> [interval]" if(@args < 3);
|
||||||
|
|
||||||
|
my ($name, $type, $id, $interval) = @args;
|
||||||
|
|
||||||
|
$interval= 60 unless defined($interval);
|
||||||
|
if( $interval < 10 ) { $interval = 60; }
|
||||||
|
|
||||||
|
|
||||||
|
$hash->{STATE} = 'Initialized';
|
||||||
|
$hash->{fhem}{interfaces}= "dimmer";
|
||||||
|
|
||||||
|
$hash->{fhem}{id} = $id;
|
||||||
|
$hash->{INTERVAL} = $interval;
|
||||||
|
|
||||||
|
$hash->{fhem}{on} = -1;
|
||||||
|
$hash->{fhem}{colormode} = '';
|
||||||
|
$hash->{fhem}{bri} = -1;
|
||||||
|
$hash->{fhem}{ct} = -1;
|
||||||
|
$hash->{fhem}{hue} = -1;
|
||||||
|
$hash->{fhem}{sat} = -1;
|
||||||
|
|
||||||
|
AssignIoPort($hash);
|
||||||
|
if(defined($hash->{IODev}->{NAME})) {
|
||||||
|
Log 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
|
||||||
|
} else {
|
||||||
|
Log 1, "$name: no I/O device";
|
||||||
|
}
|
||||||
|
|
||||||
|
#HUEDevice_GetUpdate($hash);
|
||||||
|
InternalTimer(gettimeofday()+10, "HUEDevice_GetUpdate", $hash, 0);
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub HUEDevice_Undefine($$)
|
||||||
|
{
|
||||||
|
my ($hash,$arg) = @_;
|
||||||
|
|
||||||
|
RemoveInternalTimer($hash);
|
||||||
|
|
||||||
|
delete($hash->{fhem}{id});
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub
|
||||||
|
HUEDevice_Set($@)
|
||||||
|
{
|
||||||
|
my ($hash, $name, $cmd, $value, $value2, @a) = @_;
|
||||||
|
|
||||||
|
if( $cmd eq "color" ) {
|
||||||
|
$value = int(100000/$value);
|
||||||
|
$cmd = 'ct';
|
||||||
|
} elsif( $cmd eq "toggle" ) {
|
||||||
|
$cmd = ReadingsVal($name,"state","on") eq "off" ? "on" :"off";
|
||||||
|
} elsif( $cmd =~ m/^dim(\d+)/ ) {
|
||||||
|
$value = $1 unless defined($value);
|
||||||
|
if( $value < 0 ) { $value = 0; }
|
||||||
|
if( $value > 100 ) { $value = 100; }
|
||||||
|
$cmd = 'pct';
|
||||||
|
} elsif( !defined($value) && $cmd =~ m/^(\d+)/) {
|
||||||
|
$value = $1;
|
||||||
|
if( $value > 254 ) { $value = 254; }
|
||||||
|
$cmd = 'bri';
|
||||||
|
}
|
||||||
|
|
||||||
|
# usage check
|
||||||
|
if($cmd eq 'statusRequest') {
|
||||||
|
RemoveInternalTimer($hash);
|
||||||
|
HUEDevice_GetUpdate($hash);
|
||||||
|
return undef;
|
||||||
|
} elsif($cmd eq 'on') {
|
||||||
|
|
||||||
|
my $obj = {
|
||||||
|
'on' => JSON::true,
|
||||||
|
};
|
||||||
|
if( defined($value) ) {
|
||||||
|
$obj->{transitiontime} = $value / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id}."/state",$obj);
|
||||||
|
if( $result->{'error'} )
|
||||||
|
{
|
||||||
|
$hash->{STATE} = $result->{'error'}->{'description'};
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
} elsif($cmd eq 'off') {
|
||||||
|
|
||||||
|
my $obj = {
|
||||||
|
'on' => JSON::false,
|
||||||
|
};
|
||||||
|
if( defined($value) ) {
|
||||||
|
$obj->{transitiontime} = $value / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id}."/state",$obj);
|
||||||
|
if( $result->{'error'} )
|
||||||
|
{
|
||||||
|
$hash->{STATE} = $result->{'error'}->{'description'};
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
} elsif($cmd eq "pct") {
|
||||||
|
my $obj = {
|
||||||
|
'bri' => int(2.54 * $value),
|
||||||
|
'on' => JSON::true,
|
||||||
|
};
|
||||||
|
if( defined($value2) ) {
|
||||||
|
$obj->{transitiontime} = $value2 / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id}."/state",$obj);
|
||||||
|
if( $result->{'error'} )
|
||||||
|
{
|
||||||
|
$hash->{STATE} = $result->{'error'}->{'description'};
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
} elsif($cmd eq "bri") {
|
||||||
|
my $obj = {
|
||||||
|
'bri' => 0+$value,
|
||||||
|
'on' => JSON::true,
|
||||||
|
};
|
||||||
|
|
||||||
|
my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id}."/state",$obj);
|
||||||
|
if( $result->{'error'} )
|
||||||
|
{
|
||||||
|
$hash->{STATE} = $result->{'error'}->{'description'};
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
} elsif($cmd eq "ct") {
|
||||||
|
my $obj = {
|
||||||
|
'ct' => 0+$value,
|
||||||
|
'on' => JSON::true,
|
||||||
|
};
|
||||||
|
|
||||||
|
my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id}."/state",$obj);
|
||||||
|
if( $result->{'error'} )
|
||||||
|
{
|
||||||
|
$hash->{STATE} = $result->{'error'}->{'description'};
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
} elsif($cmd eq "hue") {
|
||||||
|
my $obj = {
|
||||||
|
'hue' => 0+$value,
|
||||||
|
'on' => JSON::true,
|
||||||
|
};
|
||||||
|
|
||||||
|
my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id}."/state",$obj);
|
||||||
|
if( $result->{'error'} )
|
||||||
|
{
|
||||||
|
$hash->{STATE} = $result->{'error'}->{'description'};
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
} elsif($cmd eq "sat") {
|
||||||
|
my $obj = {
|
||||||
|
'sat' => 0+$value,
|
||||||
|
};
|
||||||
|
|
||||||
|
my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id}."/state",$obj);
|
||||||
|
if( $result->{'error'} )
|
||||||
|
{
|
||||||
|
$hash->{STATE} = $result->{'error'}->{'description'};
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
my $list = "off on toggle statusRequest";
|
||||||
|
$list .= " pct:slider,0,1,100 color:slider,2000,1,6500 bri:slider,0,1,254 ct:slider,154,1,500 hue:slider,0,1,65535 sat:slider,0,1,254" if( AttrVal($hash->{NAME}, "subType", "dimmer") eq "dimmer" );
|
||||||
|
#$list .= " dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%" if( AttrVal($hash->{NAME}, "subType", "dimmer") eq "dimmer" );
|
||||||
|
return SetExtensions($hash, $list, $name, $cmd, $value, @a);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash->{LOCAL} = 1;
|
||||||
|
HUEDevice_GetUpdate($hash);
|
||||||
|
delete $hash->{LOCAL};
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# This could be IORead in fhem, But there is none.
|
||||||
|
# Read http://forum.fhem.de/index.php?t=tree&goto=54027&rid=10#msg_54027
|
||||||
|
# to find out why.
|
||||||
|
sub
|
||||||
|
HUEDevice_ReadFromServer($@)
|
||||||
|
{
|
||||||
|
my ($hash,@a) = @_;
|
||||||
|
|
||||||
|
my $dev = $hash->{NAME};
|
||||||
|
no strict "refs";
|
||||||
|
my $ret;
|
||||||
|
unshift(@a,$dev);
|
||||||
|
$ret = IOWrite($hash, @a);
|
||||||
|
use strict "refs";
|
||||||
|
return $ret;
|
||||||
|
return if(IsDummy($dev) || IsIgnored($dev));
|
||||||
|
my $iohash = $hash->{IODev};
|
||||||
|
if(!$iohash ||
|
||||||
|
!$iohash->{TYPE} ||
|
||||||
|
!$modules{$iohash->{TYPE}} ||
|
||||||
|
!$modules{$iohash->{TYPE}}{ReadFn}) {
|
||||||
|
Log 5, "No I/O device or ReadFn found for $dev";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
no strict "refs";
|
||||||
|
#my $ret;
|
||||||
|
unshift(@a,$dev);
|
||||||
|
$ret = &{$modules{$iohash->{TYPE}}{ReadFn}}($iohash, @a);
|
||||||
|
use strict "refs";
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
my %dim_values = (
|
||||||
|
0 => "dim06%",
|
||||||
|
1 => "dim12%",
|
||||||
|
2 => "dim18%",
|
||||||
|
3 => "dim25%",
|
||||||
|
4 => "dim31%",
|
||||||
|
5 => "dim37%",
|
||||||
|
6 => "dim43%",
|
||||||
|
7 => "dim50%",
|
||||||
|
8 => "dim56%",
|
||||||
|
9 => "dim62%",
|
||||||
|
10 => "dim68%",
|
||||||
|
11 => "dim75%",
|
||||||
|
12 => "dim81%",
|
||||||
|
13 => "dim87%",
|
||||||
|
14 => "dim93%",
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
sub
|
||||||
|
HUEDevice_GetUpdate($)
|
||||||
|
{
|
||||||
|
my ($hash) = @_;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
|
||||||
|
if(!$hash->{LOCAL}) {
|
||||||
|
RemoveInternalTimer($hash);
|
||||||
|
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEDevice_GetUpdate", $hash, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $result = HUEDevice_ReadFromServer($hash,$hash->{fhem}{id});
|
||||||
|
if( !defined($result) ) {
|
||||||
|
$hash->{STATE} = "unknown";
|
||||||
|
return;
|
||||||
|
} elsif( $result->{'error'} ) {
|
||||||
|
$hash->{STATE} = $result->{'error'}->{'description'};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash->{modelid} = $result->{'modelid'};
|
||||||
|
$hash->{name} = $result->{'name'};
|
||||||
|
$hash->{type} = $result->{'type'};
|
||||||
|
$hash->{swversion} = $result->{'swversion'};
|
||||||
|
|
||||||
|
$attr{$name}{model} = $result->{'modelid'} unless (defined($attr{$name}{model}) || $result->{'modelid'} eq '' );
|
||||||
|
|
||||||
|
readingsBeginUpdate($hash);
|
||||||
|
|
||||||
|
my $state = $result->{'state'};
|
||||||
|
|
||||||
|
my $on = $state->{on};
|
||||||
|
my $colormode = $state->{'colormode'};
|
||||||
|
my $bri = $state->{'bri'};
|
||||||
|
my $ct = $state->{'ct'};
|
||||||
|
my $hue = $state->{'hue'};
|
||||||
|
my $sat = $state->{'sat'};
|
||||||
|
|
||||||
|
if( defined($colormode) && $colormode ne $hash->{fhem}{colormode} ) {readingsBulkUpdate($hash,"colormode",$colormode);}
|
||||||
|
if( defined($bri) && $bri != $hash->{fhem}{bri} ) {readingsBulkUpdate($hash,"bri",$bri);}
|
||||||
|
if( defined($ct) && $ct != $hash->{fhem}{ct} ) {
|
||||||
|
if( $ct == 0 ) {
|
||||||
|
readingsBulkUpdate($hash,"ct",$ct);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
readingsBulkUpdate($hash,"ct",$ct . " (".int(1000000/$ct)."K)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( defined($hue) && $hue != $hash->{fhem}{hue} ) {readingsBulkUpdate($hash,"hue",$hue);}
|
||||||
|
if( defined($sat) && $sat != $hash->{fhem}{sat} ) {readingsBulkUpdate($hash,"sat",$sat);}
|
||||||
|
|
||||||
|
my $s = '';
|
||||||
|
if( $on )
|
||||||
|
{
|
||||||
|
$s = 'on';
|
||||||
|
if( $on != $hash->{fhem}{on} ) {readingsBulkUpdate($hash,"onoff",1);}
|
||||||
|
|
||||||
|
my $percent = int( $state->{'bri'} * 100 / 254 );
|
||||||
|
if( $bri != $hash->{fhem}{bri} ) {readingsBulkUpdate($hash,"level", $percent . ' %');}
|
||||||
|
if( $percent > 0
|
||||||
|
&& $percent < 100 ) {
|
||||||
|
$s = $dim_values{int($percent/7)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$s = 'off';
|
||||||
|
if( $on != $hash->{fhem}{on} ) {readingsBulkUpdate($hash,"onoff",0);}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( $s ne $hash->{STATE} ) {readingsBulkUpdate($hash,"state",$s);}
|
||||||
|
readingsEndUpdate($hash,defined($hash->{LOCAL} ? 0 : 1));
|
||||||
|
|
||||||
|
$hash->{fhem}{on} = $on;
|
||||||
|
$hash->{fhem}{colormode} = $colormode;
|
||||||
|
$hash->{fhem}{bri} = $bri;
|
||||||
|
$hash->{fhem}{ct} = $ct;
|
||||||
|
$hash->{fhem}{hue} = $hue;
|
||||||
|
$hash->{fhem}{sat} = $sat;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
=pod
|
||||||
|
=begin html
|
||||||
|
|
||||||
|
<a name="HUEDevice"></a>
|
||||||
|
<h3>HUEDevice</h3>
|
||||||
|
<ul>
|
||||||
|
<br>
|
||||||
|
<a name="HUEDevice_Define"></a>
|
||||||
|
<b>Define</b>
|
||||||
|
<ul>
|
||||||
|
<code>define <name> HUEDevice <id> [<interval>]</code><br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Defines a device connected to a <a href="#HUEBridge">HUEBridge</a>.<br><br>
|
||||||
|
|
||||||
|
This can be a hue bulb, a living color light or a living whites bulb or dimmer plug.<br><br>
|
||||||
|
|
||||||
|
The device status will be updated every <interval> seconds. The default and minimum is 60.<br><br>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
<ul>
|
||||||
|
<code>define bulb HUEDevice 1</code><br>
|
||||||
|
<code>define LC HUEDevice 2</code><br>
|
||||||
|
</ul>
|
||||||
|
</ul><br>
|
||||||
|
|
||||||
|
<a name="SYSSTAT_Readings"></a>
|
||||||
|
<b>Readings</b>
|
||||||
|
<ul>
|
||||||
|
<li>bri<br>
|
||||||
|
the brightness reported from the device. the value can be betwen 1 and 254</li>
|
||||||
|
<li>colormode<br>
|
||||||
|
the current colormode</li>
|
||||||
|
<li>ct<br>
|
||||||
|
the colortemperature in mireds and kelvin</li>
|
||||||
|
<li>hue<br>
|
||||||
|
the current hue</li>
|
||||||
|
<li>level<br>
|
||||||
|
the current brightness in percent</li>
|
||||||
|
<li>onoff<br>
|
||||||
|
the current on/off state as 0 or 1</li>
|
||||||
|
<li>sat<br>
|
||||||
|
the current saturation</li>
|
||||||
|
<li>state<br>
|
||||||
|
the current state</li>
|
||||||
|
<br>
|
||||||
|
Notes:
|
||||||
|
<ul>
|
||||||
|
<li>not all readings show the actual device state. all readings not related to the current colormode have to be ignored.</li>
|
||||||
|
<li>the actual state of a device controlled by a living colors or living whites remote can be different and will
|
||||||
|
be updated after some time.</li>
|
||||||
|
</ul><br>
|
||||||
|
</ul><br>
|
||||||
|
|
||||||
|
<a name="HUEDevice_Set"></a>
|
||||||
|
<b>Set</b>
|
||||||
|
<ul>
|
||||||
|
<li>on [<ramp-time>]</li>
|
||||||
|
<li>off [<ramp-time>]</li>
|
||||||
|
<li>toggle [<ramp-time>]</li>
|
||||||
|
<li>statusRequest<br>
|
||||||
|
Request device status update.</li>
|
||||||
|
<li>pct <value> [<ramp-time>]<br>
|
||||||
|
dim to <value></li>
|
||||||
|
Notes:
|
||||||
|
<ul>
|
||||||
|
<li>the FS20 compatible dimXX% commands are also accepted.</li>
|
||||||
|
</ul><br>
|
||||||
|
<li>color <value><br>
|
||||||
|
set colortemperature to <value> kelvin.</li>
|
||||||
|
<li>bri <value><br>
|
||||||
|
set brighness to <value>; range is 1-254.</li>
|
||||||
|
<li>ct <value><br>
|
||||||
|
set colortemperature to <value> mireds; range is 154-500.</li>
|
||||||
|
<li>hue <value><br>
|
||||||
|
set hue to <value>; range is 0-65535.</li>
|
||||||
|
<li>sat <value><br>
|
||||||
|
set saturation to <value>; range is 0-254.</li>
|
||||||
|
</ul><br>
|
||||||
|
|
||||||
|
<a name="HUEDevice_Attr"></a>
|
||||||
|
<b>Attributes</b>
|
||||||
|
<ul>
|
||||||
|
<li>subType<br>
|
||||||
|
dimmer or switch, default is dimmer.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</ul><br>
|
||||||
|
|
||||||
|
=end html
|
||||||
|
=cut
|
||||||
Reference in New Issue
Block a user