# $Id$
# "Hue Personal Wireless Lighting" is a trademark owned by Koninklijke Philips Electronics N.V.,
# see www.meethue.com for more information.
# I am in no way affiliated with the Philips organization.
package main;
use strict;
use warnings;
use FHEM::Meta;
use Color;
use POSIX;
use JSON;
use SetExtensions;
use Time::Local;
#require "30_HUEBridge.pm";
#require "$attr{global}{modpath}/FHEM/30_HUEBridge.pm";
use vars qw($devcount); # Maximum device number, used for storing
use vars qw(%FW_webArgs); # all arguments specified in the GET
my %hueModels = (
LCT001 => {name => 'Hue Bulb' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'B', icon => 'hue_filled_white_and_color_e27_b22', },
LCT002 => {name => 'Hue Spot BR30' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'B', icon => 'hue_filled_br30.svg', },
LCT003 => {name => 'Hue Spot GU10' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'B', icon => 'hue_filled_gu10_par16', },
LCT007 => {name => 'Hue Bulb V2' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'B', icon => 'hue_filled_white_and_color_e27_b22', },
LCT010 => {name => 'Hue Bulb V3' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'C', icon => 'hue_filled_white_and_color_e27_b22', },
LCT011 => {name => 'Hue BR30' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'C', icon => 'hue_filled_br30.svg', },
LCT012 => {name => 'Hue color candle' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'C', },
LCT014 => {name => 'Hue Bulb V3' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'C', icon => 'hue_filled_white_and_color_e27_b22', },
LCT024 => {name => 'Hue Play' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'C', icon => 'hue_filled_play', },
LLC001 => {name => 'Living Colors G2' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_iris', },
LLC005 => {name => 'Living Colors Bloom' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_bloom', },
LLC006 => {name => 'Living Colors Gen3 Iris' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_iris', },
LLC007 => {name => 'Living Colors Gen3 Bloom' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_bloom', },
LLC010 => {name => 'Hue Living Colors Iris' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_iris', },
LLC011 => {name => 'Hue Living Colors Bloom' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_bloom', },
LLC012 => {name => 'Hue Living Colors Bloom' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_bloom', },
LLC013 => {name => 'Disney Living Colors' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_storylight', },
LLC014 => {name => 'Living Colors Aura' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_aura', },
LLC020 => {name => 'Hue Go' ,type => 'Color light' ,subType => 'extcolordimmer',
gamut => 'C', icon => 'hue_filled_go', },
LST001 => {name => 'Hue LightStrips' ,type => 'Color light' ,subType => 'colordimmer',
gamut => 'A', icon => 'hue_filled_lightstrip', },
LST002 => {name => 'Hue LightStrips Plus' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'C', icon => 'hue_filled_lightstrip', },
LWB001 => {name => 'Living Whites Bulb' ,type => 'Dimmable light' ,subType => 'dimmer',
icon => 'hue_filled_living_whites', },
LWB003 => {name => 'Living Whites Bulb' ,type => 'Dimmable light' ,subType => 'dimmer',
icon => 'hue_filled_living_whites', },
LWB004 => {name => 'Hue Lux' ,type => 'Dimmable light' ,subType => 'dimmer',
icon => 'hue_filled_white_and_color_e27_b22', },
LWB006 => {name => 'Hue Lux' ,type => 'Dimmable light' ,subType => 'dimmer',
icon => 'hue_filled_white_and_color_e27_b22', },
LWB007 => {name => 'Hue Lux' ,type => 'Dimmable light' ,subType => 'dimmer',
icon => 'hue_filled_white_and_color_e27_b22', },
LWB010 => {name => 'Hue Lux' ,type => 'Dimmable light' ,subType => 'dimmer',
icon => 'hue_filled_white_and_color_e27_b22', },
LWB014 => {name => 'Hue Lux' ,type => 'Dimmable light' ,subType => 'dimmer',
icon => 'hue_filled_white_and_color_e27_b22', },
LTW001 => {name => 'Hue A19 White Ambience' ,type => 'Color temperature light' ,subType => 'ctdimmer',
icon => 'hue_filled_white_and_color_e27_b22', },
LTW004 => {name => 'Hue A19 White Ambience' ,type => 'Color temperature light' ,subType => 'ctdimmer', },
LTW012 => {name => 'Hue ambiance candle' ,type => 'Color temperature light' ,subType => 'ctdimmer',
icon => 'hue_filled_gu10_par16', },
LTW013 => {name => 'Hue GU10 White Ambience' ,type => 'Color temperature light' ,subType => 'ctdimmer',
icon => 'hue_filled_gu10_par16', },
LTW014 => {name => 'Hue GU10 White Ambience' ,type => 'Color temperature light' ,subType => 'ctdimmer',
icon => 'hue_filled_gu10_par16', },
LLM001 => {name => 'Color Light Module' ,type => 'Extended color light' ,subType => 'extcolordimmer',
gamut => 'B', },
LLM010 => {name => 'Color Temperature Module' ,type => 'Color temperature light' ,subType => 'ctdimmer', },
LLM011 => {name => 'Color Temperature Module' ,type => 'Color temperature light' ,subType => 'ctdimmer', },
LLM012 => {name => 'Color Temperature Module' ,type => 'Color temperature light' ,subType => 'ctdimmer', },
LWL001 => {name => 'LivingWhites Outlet' ,type => 'Dimmable plug-in unit' ,subType => 'dimmer',
icon => 'hue_filled_outlet', },
LOM001 => {name => 'Hue Smart Plug' ,type => 'On/Off plug-in unit' ,subType => 'switch',
icon => 'hue_filled_plug', },
LOM002 => {name => 'Hue Smart Plug' ,type => 'On/Off plug-in unit' ,subType => 'switch',
icon => 'hue_filled_plug', },
RWL020 => {name => 'Hue Dimmer Switch' ,type => 'ZLLSwitch' ,subType => 'sensor',
icon => 'hue_filled_hds', },
RWL021 => {name => 'Hue Dimmer Switch' ,type => 'ZLLSwitch' ,subType => 'sensor',
icon => 'hue_filled_hds', },
ZGPSWITCH => {name => 'Hue Tap' ,type => 'ZGPSwitch' ,subType => 'sensor',
icon => 'hue_filled_tap', },
'FLS-H3' => {name => 'dresden elektronik FLS-H lp' ,type => 'Color temperature light' ,subType => 'ctdimmer',},
'FLS-PP3' => {name => 'dresden elektronik FLS-PP lp' ,type => 'Extended color light' ,subType => 'extcolordimmer', },
'Flex RGBW' => {name => 'LIGHTIFY Flex RGBW' ,type => 'Extended color light' ,subType => 'extcolordimmer', },
'Classic A60 RGBW' => {name => 'LIGHTIFY Classic A60 RGBW' ,type => 'Extended color light' ,subType => 'extcolordimmer', },
'CLA60 RGBW OSRAM' => {name => 'SMART+ Classic A60 RGBW' ,type => 'Extended color light' ,subType => 'extcolordimmer', },
'Gardenspot RGB' => {name => 'LIGHTIFY Gardenspot Mini RGB' ,type => 'Color light' ,subType => 'colordimmer', },
'Surface Light TW' => {name => 'LIGHTIFY Surface light tunable white' ,type => 'Color temperature light' ,subType => 'ctdimmer', },
'Classic A60 TW' => {name => 'LIGHTIFY Classic A60 tunable white' ,type => 'Color temperature light' ,subType => 'ctdimmer', },
'Classic B40 TW' => {name => 'LIGHTIFY Classic B40 tunable white' ,type => 'Color temperature light' ,subType => 'ctdimmer', },
'PAR16 50 TW' => {name => 'LIGHTIFY PAR16 50 tunable white' ,type => 'Color temperature light' ,subType => 'ctdimmer', },
'Classic A60' => {name => 'LIGHTIFY Classic A60 dimmable light' ,type => 'Dimmable Light' ,subType => 'dimmer', },
'Plug - LIGHTIFY' => {name => 'LIGHTIFY Plug' ,type => 'On/Off plug-in unit' ,subType => 'switch', },
'Plug 01' => {name => 'LIGHTIFY Plug' ,type => 'On/Off plug-in unit' ,subType => 'switch', },
'RM01' => {name => 'Busch-Jaeger ZigBee Light Link Relais', type => 'On/Off light' ,subType => 'switch', },
'DM01' => {name => 'Busch-Jaeger ZigBee Light Link Dimmer', type => 'Dimmable light' ,subType => 'dimmer', },
);
my %gamut = (
A => { r => { hue => 0, x => 0.704, y => 0.296 },
g => { hue => 100, x => 0.2151, y => 0.7106 },
b => { hue => 184, x => 0.138, y => 0.08 }, },
B => { r => { hue => 0, x => 0.675, y => 0.322 },
g => { hue => 100, x => 0.409, y => 0.518 },
b => { hue => 184, x => 0.167, y => 0.04 }, },
C => { r => { hue => 0, x => 0.692, y => 0.308 },
g => { hue => 100, x => 0.17, y => 0.7 },
b => { hue => 184, x => 0.153, y => 0.048 }, },
);
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%",
);
my $HUEDevice_hasDataDumper = 1;
sub HUEDevice_Initialize($)
{
my ($hash) = @_;
# Provide
#Consumer
$hash->{DefFn} = "HUEDevice_Define";
$hash->{UndefFn} = "HUEDevice_Undefine";
$hash->{SetFn} = "HUEDevice_Set";
$hash->{GetFn} = "HUEDevice_Get";
$hash->{AttrFn} = "HUEDevice_Attr";
$hash->{AttrList} = "IODev ".
"delayedUpdate:1 ".
"ignoreReachable:1,0 ".
"realtimePicker:1,0 ".
"color-icons:1,2 ".
"transitiontime ".
"model:".join(",", sort map { $_ =~ s/ /#/g ;$_} keys %hueModels)." ".
"setList:textField-long ".
"configList:textField-long ".
"subType:extcolordimmer,colordimmer,ctdimmer,dimmer,switch,blind ".
$readingFnAttributes;
#$hash->{FW_summaryFn} = "HUEDevice_summaryFn";
FHEM_colorpickerInit();
eval "use Data::Dumper";
$HUEDevice_hasDataDumper = 0 if($@);
return FHEM::Meta::InitMod( __FILE__, $hash );
}
sub
HUEDevice_devStateIcon($)
{
my($hash) = @_;
$hash = $defs{$hash} if( ref($hash) ne 'HASH' );
return undef if( !$hash );
my $name = $hash->{NAME};
return ".*:light_question:toggle" if( !$hash->{helper}{reachable} );
return ".*:light_question:toggle" if( ReadingsVal($name, 'mode', 'homeautomation') ne 'homeautomation' );
my $pct = ReadingsVal($name, 'pct', 100);
my $subtype = AttrVal($name, 'subType', 'extcolordimmer' );
if( $subtype eq 'blind' ) {
my $p = int(10-$pct/10)*10;
return ".*:fts_window_2w" if( $p == 0 );
return ".*:fts_shutter_$p";
}
if( $hash->{helper}->{devtype} && $hash->{helper}->{devtype} eq 'G' ) {
if( $hash->{IODev} ) {
my $createGroupReadings = AttrVal($hash->{IODev}{NAME},"createGroupReadings",undef);
if( defined($createGroupReadings) ) {
return undef if( $createGroupReadings && !AttrVal($hash->{NAME},"createGroupReadings", 1) );
return undef if( !$createGroupReadings && !AttrVal($hash->{NAME},"createGroupReadings", undef) );
return ".*:off:toggle" if( ReadingsVal($name,"onoff","0") eq "0" );
my $pct = ReadingsVal($name,"pct","100");
my $s = $dim_values{int($pct/7)};
$s="on" if( $pct eq "100" );
return ".*:$s:toggle";
}
}
#return ".*:off:toggle" if( !ReadingsVal($name,'any_on',0) );
#return ".*:on:toggle" if( ReadingsVal($name,'any_on',0) );
return undef;
}
return undef if( $hash->{helper}->{devtype} );
return ".*:off:toggle" if( ReadingsVal($name,"state","off") eq "off" );
my $s = $dim_values{int($pct/7)};
$s="on" if( $pct eq "100" );
return ".*:$s:toggle" if( AttrVal($name, "model", "") eq "LWL001" );
return ".*:$s:toggle" if( $subtype eq "dimmer" );
return ".*:$s:toggle" if( $subtype eq "switch" );
return ".*:$s@#".CommandGet("","$name RGB").":toggle" if( $pct < 100 && AttrVal($name, "color-icons", 0) == 2 );
return ".*:on@#".CommandGet("","$name rgb").":toggle" if( AttrVal($name, "color-icons", 0) != 0 );
return '
';
}
sub
HUEDevice_summaryFn($$$$)
{
my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
my $hash = $defs{$d};
my $name = $hash->{NAME};
return HUEDevice_devStateIcon($hash);
}
sub
HUEDevice_IODevChanged($$$;$)
{
my ($hash,$old,$new, $new_id) = @_;
$hash = $defs{$hash} if( ref($hash) ne 'HASH' );
my $name = $hash->{NAME};
if( $hash->{TYPE} ne 'HUEDevice' ) {
Log3 $name, 1, "$name: can't change IODev for TYPE $hash->{TYPE}";
return undef;
}
if( $new_id && $hash->{helper}->{devtype} ) {
Log3 $name, 1, "$name: can't change IODev for groups and sensors";
return undef;
}
$old = AttrVal($name, "IODev", undef) if( !$old );
my $code = $hash->{ID};
$code = $old ."-". $code if( $old );
delete $modules{HUEDevice}{defptr}{$code};
AssignIoPort($hash,$new);
if( defined($hash->{IODev}) ) {
Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
} else {
Log3 $name, 1, "$name: no I/O device";
}
$new = $hash->{IODev}->{NAME} if( defined($hash->{IODev}) );
$hash->{ID} = $new_id if( defined($new_id) );
$code = $hash->{ID};
$code = $new ."-". $code if( $new );
$modules{HUEDevice}{defptr}{$code} = $hash;
if( $old ) {
if( $new ) {
$hash->{DEF} =~ s/IODev=$old/IODev=$new/;
} else {
$hash->{DEF} =~ s/IODev=$old//;
}
} elsif( $new ) {
$hash->{DEF} .= " IODev=$new"
}
$hash->{NR} = $devcount++ if( $new_id && $new && $defs{$new}->{NR} > $hash->{NR} );
$hash->{DEF} =~ s/[^\s]+/$new_id/ if( $new_id );
$hash->{DEF} =~ s/ / /g;
addStructChange( 'modify', $name, "$name $hash->{DEF}");
return $new;
}
sub
HUEDevice_moveToBridge($$$) {
my ($serial, $new, $new_id) = @_;
my $found;
return $found if( !$serial );
return $found if( !$new_id );
foreach my $hash ( values %{$modules{HUEDevice}{defptr}} ) {
next if( !$hash->{uniqueid} );
next if( $hash->{helper}{devtype} );
next if( $serial ne $hash->{uniqueid} );
my $name = $hash->{NAME};
my $old = AttrVal( $name, 'IODev', '' );
Log3 $name, 2, "moving $name [$serial] from $old to $new";
HUEDevice_IODevChanged($hash, undef, $new, $new_id);
CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
$found = 1;
last;
}
return $found;
}
sub
HUEDevice_Define($$) {
my ($hash, $def) = @_;
return $@ unless ( FHEM::Meta::SetInternals($hash) );
my @args = split("[ \t]+", $def);
$hash->{helper}->{devtype} = "";
if( $args[2] eq "group" ) {
$hash->{helper}->{devtype} = "G";
splice( @args, 2, 1 );
} elsif( $args[2] eq "sensor" ) {
$hash->{helper}->{devtype} = "S";
splice( @args, 2, 1 );
}
my $iodev;
my $i = 0;
foreach my $param ( @args ) {
if( $param =~ m/IODev=([^\s]*)/ ) {
$iodev = $1;
splice( @args, $i, 1 );
last;
}
$i++;
}
return "Usage: define HUEDevice [group|sensor] [interval]" if(@args < 3);
my ($name, $type, $id, $interval) = @args;
$hash->{STATE} = 'Initialized';
$hash->{ID} = $hash->{helper}->{devtype}.$id;
$iodev = HUEDevice_IODevChanged( $hash, undef, $iodev ) if( !$hash->{IODev} );
my $code = $hash->{ID};
$code = $iodev ."-". $code if( defined($iodev) );
my $d = $modules{HUEDevice}{defptr}{$code};
return "HUEDevice device $hash->{ID} on HUEBridge $iodev already defined as $d->{NAME}."
if( defined($d)
&& $d->{IODev} && $hash->{IODev} && $d->{IODev} == $hash->{IODev}
&& $d->{NAME} ne $name );
$modules{HUEDevice}{defptr}{$code} = $hash;
if( AttrVal($iodev, "pollDevices", 1) ) {
$interval = undef unless defined($interval);
} elsif( !$hash->{helper}->{devtype} || $hash->{helper}->{devtype} ne 'G' ) {
$interval = 60 unless defined($interval);
}
$args[3] = "" if( !defined( $args[3] ) );
if( !$hash->{helper}->{devtype} ) {
$hash->{DEF} = "$id $args[3] IODev=$iodev" if( $iodev );
$interval = 60 if( defined($interval) && $interval < 10 );
$hash->{INTERVAL} = $interval;
$hash->{helper}{on} = -1;
$hash->{helper}{reachable} = undef;
$hash->{helper}{colormode} = '';
$hash->{helper}{bri} = -1;
$hash->{helper}{ct} = -1;
$hash->{helper}{hue} = -1;
$hash->{helper}{sat} = -1;
$hash->{helper}{xy} = '';
$hash->{helper}{alert} = '';
$hash->{helper}{effect} = '';
$hash->{helper}{pct} = -1;
$hash->{helper}{rgb} = "";
$hash->{helper}{battery} = -1;
$hash->{helper}{mode} = '';
$attr{$name}{devStateIcon} = '{(HUEDevice_devStateIcon($name),"toggle")}' if( !defined( $attr{$name}{devStateIcon} ) );
my $icon_path = AttrVal("WEB", "iconPath", "default:fhemSVG:openautomation" );
$attr{$name}{'color-icons'} = 2 if( !defined( $attr{$name}{'color-icons'} ) && $icon_path =~ m/openautomation/ );
} elsif( $hash->{helper}->{devtype} eq 'G' ) {
$hash->{DEF} = "group $id $args[3] IODev=$iodev" if( $iodev );
$interval = 60 if( defined($interval) && $interval < 10 );
$hash->{INTERVAL} = $interval;
$hash->{helper}{all_on} = -1;
$hash->{helper}{any_on} = -1;
$attr{$name}{delayedUpdate} = 1 if( !defined( $attr{$name}{delayedUpdate} ) );
$attr{$name}{devStateIcon} = '{(HUEDevice_devStateIcon($name),"toggle")}' if( !defined( $attr{$name}{devStateIcon} ) );
my $icon_path = AttrVal("WEB", "iconPath", "default:fhemSVG:openautomation" );
$attr{$name}{'color-icons'} = 2 if( !defined( $attr{$name}{'color-icons'} ) && $icon_path =~ m/openautomation/ );
addToDevAttrList($name, "createActionReadings:1,0");
addToDevAttrList($name, "createGroupReadings:1,0");
} elsif( $hash->{helper}->{devtype} eq 'S' ) {
$hash->{DEF} = "sensor $id $args[3] IODev=$iodev" if( $iodev );
$interval = 60 if( defined($interval) && $interval < 1 );
$hash->{INTERVAL} = $interval;
}
RemoveInternalTimer($hash);
if( $init_done ) {
HUEDevice_GetUpdate($hash);
} else {
InternalTimer(gettimeofday()+10, "HUEDevice_GetUpdate", $hash, 0);
}
return undef;
}
sub HUEDevice_Undefine($$)
{
my ($hash,$arg) = @_;
RemoveInternalTimer($hash);
my $code = $hash->{ID};
$code = $hash->{IODev}->{NAME} ."-". $code if( defined($hash->{IODev}) );
delete($modules{HUEDevice}{defptr}{$code});
return undef;
}
sub
HUEDevice_SetParam($$@)
{
my ($name, $obj, $cmd, $value, $value2) = @_;
if( $cmd eq "color" ) {
$value = int(1000000/$value);
$cmd = 'ct';
} elsif( $name && $cmd eq "toggle" ) {
$cmd = ReadingsVal($name,"onoff",1) ? "off" :"on";
} elsif( $cmd =~ m/^dim(\d+)/ ) {
$value2 = $value;
$value = $1;
$value = 0 if( $value < 0 );
$value = 100 if( $value > 100 );
$cmd = 'pct';
} elsif( !defined($value) && $cmd =~ m/^(\d+)/) {
$value2 = $value;
$value = $1;
$value = 0 if( $value < 0 );
$value = 254 if( $value > 254 );
$cmd = 'bri';
}
my $subtype = "extcolordimmer";
if( $name ) {
$subtype = AttrVal($name, "subType", $subtype);
if( $cmd eq 'up' ) {
$cmd = 'pct';
$value = 100;
} elsif( $cmd eq 'down' ) {
$cmd = 'pct';
$value = 0;
} elsif( $cmd eq 'pct' && $value == 0 && $subtype ne 'blind' ) {
$cmd = "off";
$value = $value2;
}
}
if($cmd eq 'on') {
$obj->{'on'} = JSON::true;
# temporary disablea for everything. hast do be disabled for groups.
# see https://forum.fhem.de/index.php/topic,11020.msg497825.html#msg497825
#$obj->{'bri'} = 254 if( $name && ReadingsVal($name,"bri","0") eq 0 && AttrVal($name, 'subType', 'dimmer') ne 'switch' );
$obj->{'transitiontime'} = $value * 10 if( defined($value) );
} elsif($cmd eq 'off') {
$obj->{'on'} = JSON::false;
$obj->{'transitiontime'} = $value * 10 if( defined($value) );
} elsif($cmd eq "pct") {
if( $subtype eq 'blind' ) {
$obj->{'pct'} = int($value);
}
my $bri;
if( $value > 50 ) {
$bri = 2.57 * ($value-50) + 128;
} else {
$bri = 2.59 * ($value-50) + 128;
}
$bri = 0 if( $bri < 0 );
$bri = 254 if( $bri > 254 );
$obj->{'on'} = JSON::true;
$obj->{'bri'} = int($bri);
$obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
} elsif($cmd eq "bri") {
#$value = 8 if( $value < 8 && AttrVal($name, "model", "") eq "LWL001" );
$obj->{'on'} = JSON::true;
$obj->{'bri'} = 0+$value;
$obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
} 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'} = 0+$value if( defined($value) );
#$obj->{'transitiontime'} = 1;
#$defs{$name}->{helper}->{update_timeout} = 0;
} else {
my $bri = ReadingsVal($name,"bri","0");
$bri += 25;
$bri = 254 if( $bri > 254 );
$obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
$obj->{'bri'} = 0+$bri;
$obj->{'transitiontime'} = 1;
#$obj->{'transitiontime'} = $value * 10 if( defined($value) );
$defs{$name}->{helper}->{update_timeout} = 0;
}
} 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'} = 0-$value if( defined($value) );
#$obj->{'transitiontime'} = 1;
#$defs{$name}->{helper}->{update_timeout} = 0;
} else {
my $bri = ReadingsVal($name,"bri","0");
$bri -= 25;
$bri = 0 if( $bri < 0 );
$obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
$obj->{'bri'} = 0+$bri;
$obj->{'transitiontime'} = 1;
#$obj->{'transitiontime'} = $value * 10 if( defined($value) );
$defs{$name}->{helper}->{update_timeout} = 0;
}
} elsif($cmd eq "satUp") {
$obj->{'on'} = JSON::true if( $name && !$defs{$name}->{helper}{on} );
$obj->{'sat_inc'} = 25;
$obj->{'sat_inc'} = 0+$value if( defined($value) );
} elsif($cmd eq "satDown") {
$obj->{'on'} = JSON::true if( $name && !$defs{$name}->{helper}{on} );
$obj->{'sat_inc'} = -25;
$obj->{'sat_inc'} = 0+$value if( defined($value) );
} elsif($cmd eq "hueUp") {
$obj->{'on'} = JSON::true if( $name && !$defs{$name}->{helper}{on} );
$obj->{'hue_inc'} = 6553;
$obj->{'hue_inc'} = 0+$value if( defined($value) );
} elsif($cmd eq "hueDown") {
$obj->{'on'} = JSON::true if( $name && !$defs{$name}->{helper}{on} );
$obj->{'hue_inc'} = -6553;
$obj->{'hue_inc'} = 0+$value if( defined($value) );
} elsif($cmd eq "ctUp") {
$obj->{'on'} = JSON::true if( $name && !$defs{$name}->{helper}{on} );
$obj->{'ct_inc'} = 16;
$obj->{'ct_inc'} = 0+$value if( defined($value) );
} elsif($cmd eq "ctDown") {
$obj->{'on'} = JSON::true if( $name && !$defs{$name}->{helper}{on} );
$obj->{'ct_inc'} = -16;
$obj->{'ct_inc'} = 0+$value if( defined($value) );
} elsif($cmd eq "ct") {
$obj->{'on'} = JSON::true;
$value = int(1000000/$value) if( $value > 1000 );
$obj->{'ct'} = 0+$value;
$obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
} elsif($cmd eq "hue") {
$obj->{'on'} = JSON::true;
$obj->{'hue'} = 0+$value;
$obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
} elsif($cmd eq "sat") {
$obj->{'on'} = JSON::true;
$obj->{'sat'} = 0+$value;
$obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
} elsif($cmd eq "xy" && $value =~ m/^(.+),(.+)/) {
my ($x,$y) = ($1, $2);
$obj->{'on'} = JSON::true;
$obj->{'xy'} = [0+$x, 0+$y];
$obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
} elsif( $cmd eq "rgb" && $value =~ m/^(..)(..)(..)/) {
my( $r, $g, $b ) = (hex($1)/255.0, hex($2)/255.0, hex($3)/255.0);
my $hash = $defs{$name};
if( $name && ( !AttrVal($name, "model", undef)
|| AttrVal($name, "model", undef) eq 'LLC020'
|| ($hash && $hash->{IODev} && $hash->{IODev}{TYPE} eq 'tradfri' ) ) ) {
my( $h, $s, $v ) = Color::rgb2hsv($r,$g,$b);
$obj->{'on'} = JSON::true;
$obj->{'hue'} = int( $h * 65535 );
$obj->{'sat'} = int( $s * 254 );
$obj->{'bri'} = int( $v * 254 );
} else {
# calculation from http://www.everyhue.com/vanilla/discussion/94/rgb-to-xy-or-hue-sat-values/p1
my $X = 1.076450 * $r - 0.237662 * $g + 0.161212 * $b;
my $Y = 0.410964 * $r + 0.554342 * $g + 0.034694 * $b;
my $Z = -0.010954 * $r - 0.013389 * $g + 1.024343 * $b;
#Log3 $name, 3, "rgb: ". $r . " " . $g ." ". $b;
#Log3 $name, 3, "XYZ: ". $X . " " . $Y ." ". $Y;
if( $X != 0
|| $Y != 0
|| $Z != 0 ) {
my $x = $X / ($X + $Y + $Z);
my $y = $Y / ($X + $Y + $Z);
#Log3 $name, 3, "xyY:". $x . " " . $y ." ". $Y;
$Y = 1 if( $Y > 1 );
$x = 0 if( $x < 0);
$x = 1 if( $x > 1);
$y = 0 if( $y < 0);
$y = 1 if( $y > 1);
my $bri = maxNum($r,$g,$b);
#my $bri = $Y;
$obj->{'on'} = JSON::true;
$obj->{'xy'} = [0+$x, 0+$y];
$obj->{'bri'} = int(254*$bri);
} else {
$obj->{'on'} = JSON::false;
}
}
} elsif( $cmd eq "hsv" && $value =~ m/^(..)(..)(..)/) {
my( $h, $s, $v ) = (hex($1), hex($2), hex($3));
$s = 254 if( $s > 254 );
$v = 254 if( $v > 254 );
$obj->{'on'} = JSON::true;
$obj->{'hue'} = int($h*256);
$obj->{'sat'} = 0+$s;
$obj->{'bri'} = 0+$v;
} elsif( $cmd eq "alert" ) {
$obj->{'alert'} = $value;
} elsif( $cmd eq "effect" ) {
$obj->{'effect'} = $value;
} elsif( $cmd eq "transitiontime" ) {
$obj->{'transitiontime'} = 0+$value;
} elsif( $name && $cmd eq "delayedUpdate" ) {
$defs{$name}->{helper}->{update_timeout} = 1;
} elsif( $name && $cmd eq "immediateUpdate" ) {
$defs{$name}->{helper}->{update_timeout} = 0;
} elsif( $name && $cmd eq "noUpdate" ) {
$defs{$name}->{helper}->{update_timeout} = -1;
} elsif( $cmd eq 'stop' && $subtype eq 'blind' ) {
$obj->{stop} = JSON::true;
} elsif( $cmd eq 'habridgeupdate' ) {
$obj->{habridgeupdate} = JSON::true;
} else {
return 0;
}
return 1;
}
sub HUEDevice_Set($@);
sub
HUEDevice_Set($@)
{
my ($hash, $name, @aa) = @_;
my ($cmd, @args) = @aa;
my %obj;
my @match;
my $entries;
$hash->{helper}->{update_timeout} = AttrVal($name, "delayedUpdate", 1);
if( $hash->{helper}->{devtype} eq 'G' ) {
if( $cmd eq 'lights' ) {
return "usage: lights " if( @args != 1 );
my $obj = { 'lights' => HUEBridge_string2array($args[0]), };
my $result = HUEDevice_ReadFromServer($hash,$hash->{ID},$obj);
if( $result->{success} ) {
RemoveInternalTimer($hash);
HUEDevice_GetUpdate($hash);
}
return $result->{error}{description} if( $result->{error} );
return undef;
} elsif( $cmd eq 'savescene' ) {
if( $hash->{IODev} && $hash->{IODev}{helper}{apiversion} && $hash->{IODev}{helper}{apiversion} >= (1<<16) + (11<<8) ) {
return "usage: savescene " if( @args < 1 );
return fhem( "set $hash->{IODev}{NAME} savescene ". join( ' ', @aa[1..@aa-1]). " $hash->{NAME}" );
} else {
return "usage: savescene " if( @args != 1 );
return fhem( "set $hash->{IODev}{NAME} savescene $aa[1] $aa[1] $hash->{NAME}" );
}
} elsif( $cmd eq 'deletescene' ) {
return "usage: deletescene " if( @args != 1 );
return fhem( "set $hash->{IODev}{NAME} deletescene $aa[1]" );
} elsif( $cmd eq 'scene' ) {
return "usage: scene |" if( !@args );
my $arg = join( ' ', @args );
my $deConz;
if( $hash->{IODev} && $hash->{IODev}{TYPE} eq 'HUEBridge' ) {
if( defined($hash->{IODev}{modelid}) && $hash->{IODev}{modelid} eq 'deCONZ' ) {
$deConz = 1;
$arg = HUEBridge_scene2id_deCONZ($hash, $arg);
} else {
$arg = HUEBridge_scene2id($hash->{IODev}, $arg);
}
}
my $obj = {'scene' => $arg};
$hash->{helper}->{update} = 1;
my $result;
$result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/action",$obj) if( !$deConz );
$result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/scenes/$arg/recall",$obj) if( $deConz );
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' ) {
my $shash = $hash->{IODev};
my $id = $hash->{ID};
$id = $1 if( $id =~ m/^S(\d.*)/ );
$hash->{".triggerUsed"} = 1;
if( $cmd eq "statusRequest" ) {
RemoveInternalTimer($hash);
HUEDevice_GetUpdate($hash);
return undef;
} elsif( $cmd eq 'json' ) {
return "usage: json [setsensor|configsensor] " if( !@args );
my $type = 'setsensor';
if( $args[0] eq 'setsensor' || $args[0] eq 'configsensor' ) {
$type = shift @args;
}
return HUEBridge_Set( $shash, $shash->{NAME}, $type, $id, @args );
return undef;
} elsif( @match = grep { $cmd eq $_ } keys %{($hash->{helper}{setList}{cmds}?$hash->{helper}{setList}{cmds}:{})} ) {
return HUEBridge_Set( $shash, $shash->{NAME}, 'setsensor', $id, $hash->{helper}{setList}{cmds}{$match[0]} );
} elsif( $entries = $hash->{helper}{setList}{regex} ) {
foreach my $entry (@{$entries}) {
if( join(' ', @aa) =~ /$entry->{regex}/ ) {
my $VALUE1 = $1;
my $VALUE2 = $2;
my $VALUE3 = $3;
my $json = $entry->{json};
if( $json =~ m/^perl:\{(.*)\}$/ ) {
$json = eval $json;
if($@) {
Log3 $name, 3, "$name: configList: ". join(' ', @aa). ": ". $@;
return "error: ". join(' ', @aa). ": ". $@;
}
} else {
$json =~ s/\$1/$VALUE1/;
$json =~ s/\$2/$VALUE2/;
$json =~ s/\$3/$VALUE3/;
}
return HUEBridge_Set( $shash, $shash->{NAME}, 'setsensor', $id, $json );
}
}
} elsif( @match = grep { $cmd eq $_ } keys %{($hash->{helper}{configList}{cmds}?$hash->{helper}{configList}{cmds}:{})} ) {
return HUEBridge_Set( $shash, $shash->{NAME}, 'configsensor', $id, $hash->{helper}{configList}{cmds}{$match[0]} );
} elsif( $entries = $hash->{helper}{configList}{regex} ) {
foreach my $entry (@{$entries}) {
if( join(' ', @aa) =~ /$entry->{regex}/ ) {
my $VALUE1 = $1;
my $VALUE2 = $2;
my $VALUE3 = $3;
my $json = $entry->{json};
if( $json =~ m/^perl:\{(.*)\}$/ ) {
$json = eval $json;
if($@) {
Log3 $name, 3, "$name: configList: ". join(' ', @aa). ": ". $@;
return "error: ". join(' ', @aa). ": ". $@;
}
} else {
$json =~ s/\$1/$VALUE1/;
$json =~ s/\$2/$VALUE2/;
$json =~ s/\$3/$VALUE3/;
}
return HUEBridge_Set( $shash, $shash->{NAME}, 'configsensor', $id, $json );
}
}
}
my $list = 'statusRequest:noArg';
$list .= ' json' if( $hash->{type} && $hash->{type} =~ /^CLIP/ );
$list .= ' '. join( ':noArg ', keys %{$hash->{helper}{setList}{cmds}} ) if( $hash->{helper}{setList}{cmds} );
$list .= ':noArg' if( $hash->{helper}{setList}{cmds} );
if( my $entries = $hash->{helper}{setList}{regex} ) {
foreach my $entry (@{$entries}) {
$list .= ' ';
$list .= (split( ' ', $entry->{regex} ))[0];
}
}
$list .= ' '. join( ':noArg ', keys %{$hash->{helper}{configList}{cmds}} ) if( $hash->{helper}{configList}{cmds} );
$list .= ':noArg' if( $hash->{helper}{configList}{cmds} );
if( my $entries = $hash->{helper}{configList}{regex} ) {
foreach my $entry (@{$entries}) {
$list .= ' ';
$list .= (split( ' ', $entry->{regex} ))[0];
}
}
return SetExtensions($hash, $list, $name, @aa);
}
if( $cmd eq 'rename' ) {
my $new_name = join( ' ', @aa[1..@aa-1]);
my $obj = { 'name' => $new_name, };
my $result = HUEDevice_ReadFromServer($hash,$hash->{ID},$obj);
if( $result->{success} ) {
RemoveInternalTimer($hash);
HUEDevice_GetUpdate($hash);
CommandAttr(undef,"$name alias $new_name");
CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
}
return $result->{error}{description} if( $result->{error} );
return undef;
}
if( (my $joined = join(" ", @aa)) =~ /:/ ) {
$joined =~ s/on-till\s+[^\s]+//g; #bad workaround for: https://forum.fhem.de/index.php/topic,61636.msg728557.html#msg728557
my @cmds = split(":", $joined);
for( my $i = 0; $i <= $#cmds; ++$i ) {
HUEDevice_SetParam($name, \%obj, split(" ", $cmds[$i]) );
}
} else {
my ($cmd, $value, $value2, @a) = @aa;
if( $cmd eq "statusRequest" ) {
RemoveInternalTimer($hash);
HUEDevice_GetUpdate($hash);
return undef;
}
HUEDevice_SetParam($name, \%obj, $cmd, $value, $value2);
}
if( %obj ) {
if( defined($obj{on}) ) {
$hash->{desired} = $obj{on}?1:0;
}
if( !defined($obj{transitiontime}) ) {
my $transitiontime = AttrVal($name, "transitiontime", undef);
$obj{transitiontime} = 0 + $transitiontime if( defined( $transitiontime ) );
}
}
# if( $hash->{helper}->{update_timeout} == -1 ) {
# my $diff;
# my ($seconds, $microseconds) = gettimeofday();
# if( $hash->{helper}->{timestamp} ) {
# my ($seconds2, $microseconds2) = @{$hash->{helper}->{timestamp}};
#
# $diff = (($seconds-$seconds2)*1000000 + $microseconds-$microseconds2)/1000;
# }
# $hash->{helper}->{timestamp} = [$seconds, $microseconds];
#
# return undef if( $diff < 100 );
# }
if( scalar keys %obj ) {
my $result;
if( $hash->{helper}->{devtype} eq 'G' ) {
$hash->{helper}->{update} = 1;
$result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/action",\%obj);
} elsif( defined( $obj{habridgeupdate} ) ) {
$result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/bridgeupdatestate",\%obj);
} else {
$result = HUEDevice_ReadFromServer($hash,"$hash->{ID}/state",\%obj);
}
SetExtensionsCancel($hash);
if( defined($result) && $result->{'error'} ) {
$hash->{STATE} = $result->{'error'}->{'description'};
return undef;
}
$hash->{".triggerUsed"} = 1;
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;
}
my $subtype = AttrVal($name, "subType", "extcolordimmer");
my $list = "off:noArg on:noArg toggle:noArg statusRequest:noArg";
$list .= " pct:colorpicker,BRI,0,1,100 bri:colorpicker,BRI,0,1,254" if( $subtype =~ m/dimmer/ );
$list .= " rgb:colorpicker,RGB" if( $subtype =~ m/color/ );
$list .= " color:colorpicker,CT,2000,1,6500 ct:colorpicker,CT,154,1,500" if( $subtype =~ m/ct|ext/ );
$list .= " hue:colorpicker,HUE,0,1,65535 sat:slider,0,1,254 xy" if( $subtype =~ m/color/ );
$list = 'up:noArg stop:noArg down:noArg pct:colorpicker,BRI,0,1,100' if( $subtype eq 'blind' );
if( $hash->{IODev} && $hash->{IODev}{helper}{apiversion} && $hash->{IODev}{helper}{apiversion} >= (1<<16) + (7<<8) ) {
$list .= " dimUp:noArg dimDown:noArg" if( $subtype =~ m/dimmer/ );
$list .= " ctUp:noArg ctDown:noArg" if( $subtype =~ m/ct|ext/ );
$list .= " hueUp:noArg hueDown:noArg satUp:noArg satDown:noArg" if( $subtype =~ m/color/ );
} elsif( !$hash->{helper}->{devtype} && $subtype =~ m/dimmer/ ) {
$list .= " dimUp:noArg dimDown:noArg";
}
#$list .= " dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%" if( $subtype =~ m/dimmer/ );
if( $hash->{IODev} && $hash->{IODev}{TYPE} eq 'HUEBridge' ) {
$list .= " alert:none,select,lselect";
$list .= ",breathe,okay,channelchange,finish,stop" if( defined($hash->{IODev}{modelid}) && $hash->{IODev}{modelid} eq 'deCONZ' );
$list .= " effect:none,colorloop" if( $subtype =~ m/color/ );
$list .= " lights" if( $hash->{helper}->{devtype} eq 'G' );
$list .= " rename";
if( $hash->{helper}->{devtype} eq 'G' ) {
$list .= " savescene deletescene";
}
}
if( $hash->{IODev} && defined($hash->{IODev}{modelid}) && $hash->{IODev}{modelid} eq 'deCONZ' ) {
if( my $scenes = $hash->{helper}{scenes} ) {
my @names;
for my $scene (@{$scenes}) {
push(@names, $scene->{name});
}
# my $s_scenes = join (",",(my $names = map { $_->{name}} @$scenes));
my $scenes = join (',', @names);
$scenes =~ s/ /#/g;
$list .= " scene:". $scenes;
}
} elsif( my $scenes = $hash->{IODev}{helper}{scenes} ) {
local *containsOneOfMyLights = sub($) {
return 1 if( !defined($hash->{helper}{lights}) );
my( $lights ) = @_;
foreach my $light (@{$lights}) {
return 1 if( defined($hash->{helper}{lights}{$light}) );
}
return 0;
};
my %count;
map { $count{$scenes->{$_}{name}}++ } keys %{$scenes};
$list .= " scene:". join(",", sort grep { defined } map { if( !containsOneOfMyLights($scenes->{$_}{lights}) ) {
undef;
} else {
my $scene = $scenes->{$_}{name};
if( $count{$scene} > 1 ) {
$scene .= " [id=$_]";
}
$scene =~ s/ /#/g; $scene;
}
} keys %{$scenes} );
} else {
$list .= " scene";
}
return SetExtensions($hash, $list, $name, @aa);
}
sub
HUEDevice_cttorgb($)
{
my ($ct) = @_;
# calculation from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code
# adjusted by 1000K
my $temp = (1000000/$ct)/100 + 10;
my $r = 0;
my $g = 0;
my $b = 0;
$r = 255;
$r = 329.698727446 * ($temp - 60) ** -0.1332047592 if( $temp > 66 );
$r = 0 if( $r < 0 );
$r = 255 if( $r > 255 );
if( $temp <= 66 ) {
$g = 99.4708025861 * log($temp) - 161.1195681661;
} else {
$g = 288.1221695283 * ($temp - 60) ** -0.0755148492;
}
$g = 0 if( $g < 0 );
$g = 255 if( $g > 255 );
$b = 255;
$b = 0 if( $temp <= 19 );
if( $temp < 66 ) {
$b = 138.5177312231 * log($temp-10) - 305.0447927307;
}
$b = 0 if( $b < 0 );
$b = 255 if( $b > 255 );
return( $r, $g, $b );
}
sub
HUEDevice_xyYtorgb($$$)
{
# calculation from http://www.brucelindbloom.com/index.html
my ($x,$y,$Y) = @_;
#Log 3, "xyY:". $x . " " . $y ." ". $Y;
my $r = 0;
my $g = 0;
my $b = 0;
if( $y > 0 ) {
my $X = $x * $Y / $y;
my $Z = (1 - $x - $y)*$Y / $y;
if( $X > 1
|| $Y > 1
|| $Z > 1 ) {
my $f = maxNum($X,$Y,$Z);
$X /= $f;
$Y /= $f;
$Z /= $f;
}
#Log 3, "XYZ: ". $X . " " . $Y ." ". $Y;
$r = 0.7982 * $X + 0.3389 * $Y - 0.1371 * $Z;
$g = -0.5918 * $X + 1.5512 * $Y + 0.0406 * $Z;
$b = 0.0008 * $X + 0.0239 * $Y + 0.9753 * $Z;
if( $r > 1
|| $g > 1
|| $b > 1 ) {
my $f = maxNum($r,$g,$b);
$r /= $f;
$g /= $f;
$b /= $f;
}
#Log 3, "rgb: ". $r . " " . $g ." ". $b;
$r *= 255;
$g *= 255;
$b *= 255;
}
return( $r, $g, $b );
}
sub
HUEDevice_Get($@)
{
my ($hash, @a) = @_;
my $name = $a[0];
return "$name: get needs at least one parameter" if(@a < 2);
my $cmd= $a[1];
if($cmd eq "rgb") {
my $r = 0;
my $g = 0;
my $b = 0;
my $cm = ReadingsVal($name,"colormode","");
if( $cm eq "ct" ) {
if( ReadingsVal($name,"ct","") =~ m/(\d+) .*/ ) {
($r,$g,$b) = HUEDevice_cttorgb($1);
}
} elsif( $cm eq "hs" ) {
my $h = ReadingsVal($name,"hue",0) / 65535.0;
my $s = ReadingsVal($name,"sat",0) / 254.0;
my $v = ReadingsVal($name,"bri",0) / 254.0;
($r,$g,$b) = Color::hsv2rgb($h,$s,$v);
$r *= 255;
$g *= 255;
$b *= 255;
} elsif( ReadingsVal($name,"xy","") =~ m/(.+),(.+)/ ) {
my ($x,$y) = ($1, $2);
my $Y = ReadingsVal($name,"bri",0) / 254.0;
($r,$g,$b) = HUEDevice_xyYtorgb($x,$y,$Y);
}
return sprintf( "%02x%02x%02x", $r+0.5, $g+0.5, $b+0.5 );
} elsif($cmd eq "RGB") {
my $r = 0;
my $g = 0;
my $b = 0;
my $cm = ReadingsVal($name,"colormode","");
if( $cm eq "ct" ) {
if( ReadingsVal($name,"ct","") =~ m/(\d+) .*/ ) {
($r,$g,$b) = HUEDevice_cttorgb($1);
}
} elsif( $cm eq "hs" ) {
my $h = ReadingsVal($name,"hue",0) / 65535.0;
my $s = ReadingsVal($name,"sat",0) / 254.0;
my $v = 1;
($r,$g,$b) = Color::hsv2rgb($h,$s,$v);
$r *= 255;
$g *= 255;
$b *= 255;
} elsif( ReadingsVal($name,"xy","") =~ m/(.+),(.+)/ ) {
my ($x,$y) = ($1, $2);
my $Y = 1;
($r,$g,$b) = HUEDevice_xyYtorgb($x,$y,$Y);
}
return sprintf( "%02x%02x%02x", $r+0.5, $g+0.5, $b+0.5 );
} elsif ( $cmd eq "startup" ) {
my $result = IOWrite($hash,undef,$hash->{NAME},$hash->{ID});
return $result->{error}{description} if( $result->{error} );
return "not supported" if( !$result->{config} || !$result->{config}{startup} );
return "$result->{config}{startup}{mode}\t$result->{config}{startup}{configured}";
return Dumper $result->{config}{startup};
} elsif ( $cmd eq "devStateIcon" ) {
return HUEDevice_devStateIcon($hash);
}
my $list;
$list .= "rgb:noArg RGB:noArg devStateIcon:noArg" if( $hash->{helper}->{devtype} ne 'S' );
if( my $subtype = $attr{$name}{subType} ) {
$list = ' devStateIcon:noArg' if( $subtype eq 'blind' );
}
if( $hash->{IODev} && $hash->{IODev}{helper}{apiversion} && $hash->{IODev}{helper}{apiversion} >= (1<<16) + (26<<8) ) {
$list .= " startup:noArg";
}
return "Unknown argument $cmd, choose one of $list";
}
###################################
# 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 $name = $hash->{NAME};
#return if(IsDummy($name) || IsIgnored($name));
no strict "refs";
my $ret;
unshift(@a,$name);
#$ret = IOWrite($hash, @a);
$ret = IOWrite($hash,$hash,@a);
use strict "refs";
return $ret;
}
sub
HUEDevice_GetUpdate($)
{
my ($hash) = @_;
my $name = $hash->{NAME};
if( $hash->{helper}->{devtype} eq 'G' ) {
my $result = HUEDevice_ReadFromServer($hash,$hash->{ID});
if( !defined($result) ) {
$hash->{STATE} = "unknown";
return;
} elsif( $result->{'error'} ) {
$hash->{STATE} = $result->{'error'}->{'description'};
return;
}
HUEDevice_Parse($hash, $result);
} elsif( $hash->{helper}->{devtype} eq 'S' ) {
}
if(!$hash->{LOCAL}) {
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEDevice_GetUpdate", $hash, 0) if( $hash->{INTERVAL} );
}
return undef if( $hash->{helper}->{devtype} eq 'G' );
my $result = HUEDevice_ReadFromServer($hash,$hash->{ID});
if( !defined($result) ) {
$hash->{helper}{reachable} = 0;
#$hash->{STATE} = "unknown";
return;
} elsif( $result->{'error'} ) {
$hash->{helper}{reachable} = 0;
$hash->{STATE} = $result->{'error'}->{'description'};
return;
}
HUEDevice_Parse($hash, $result);
HUEBridge_updateGroups($hash->{IODev}, $hash->{ID}) if( $hash->{IODev} && ( $hash->{IODev}{TYPE} eq 'HUEBridge'
|| $hash->{IODev}{TYPE} eq 'tradfri' ) );
}
sub
HUEDeviceSetIcon($;$)
{
my ($hash,$force) = @_;
$hash = $defs{$hash} if( ref($hash) ne 'HASH' );
return undef if( !$hash );
my $name = $hash->{NAME};
return if( defined($attr{$name}{icon}) && !$force );
if( defined($hash->{modelid}) ) {
my $model = $hueModels{$hash->{modelid}};
return undef if( !$model );
my $icon = $model->{icon};
return undef if( !$icon );
$attr{$name}{icon} = $icon;
} elsif( $hash->{class} ) {
my $class = lc( $hash->{class} );
$class =~ s/ room//;
$class =~ s/ /_/;
$attr{$name}{icon} = "hue_room_$class";
} elsif( defined($hash->{helper}{json}) && defined($hash->{helper}{json}{config}) ) {
my $archetype = $hash->{helper}{json}{config}{archetype};
# TODO ...
}
}
sub
HUEDevice_Parse($$)
{
my($hash,$result) = @_;
my $name = $hash->{NAME};
if( ref($result) ne "HASH" ) {
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: $result";
} else {
Log3 $name, 2, "$name: got wrong status message for $name: $result";
}
return undef;
}
Log3 $name, 4, "parse status message for $name";
#Log3 $name, 5, Dumper $result if($HUEDevice_hasDataDumper);
$hash->{name} = $result->{name} if( defined($result->{name}) );
$hash->{type} = $result->{type} if( defined($result->{type}) );
$hash->{class} = $result->{class} if( defined($result->{class}) );
$hash->{uniqueid} = $result->{uniqueid} if( defined($result->{uniqueid}) );
$hash->{helper}{json} = $result;
if( $hash->{helper}->{devtype} eq 'G' ) {
#if( !defined($attr{$name}{subType}) && $hash->{type} ) {
# if( $hash->{type} eq 'Room' ) {
# $attr{$name}{subType} = 'room';
#
# } elsif( $hash->{type} eq 'LightGroup' ) {
# $attr{$name}{subType} = 'lightgroup';
# }
#}
$hash->{helper}{scenes} = $result->{scenes} if( defined($result->{scenes}) );
if( $result->{lights} ) {
$hash->{helper}{lights} = {map {$_=>1} @{$result->{lights}}};
$hash->{lights} = join( ",", sort { $a <=> $b } @{$result->{lights}} );
} else {
$hash->{helper}{lights} = {};
$hash->{lights} = '';
}
if( ref($result->{state}) eq 'HASH' ) {
my %readings;
if( $result->{stream}
&& (defined($hash->{helper}{stream_active}) || $result->{stream}{active}) ) {
$readings{stream_active} = $result->{stream}{active}?1:0;
}
if( $result->{state} ) {
$readings{all_on} = $result->{state}{all_on};
$readings{any_on} = $result->{state}{any_on};
}
if( AttrVal($name, 'createActionReadings', 0) ) {
if( my $state = $result->{action} ) {
$readings{ct} = $state->{ct}; $readings{ct} .= " (".int(1000000/$readings{ct})."K)" if( $readings{ct} );
$readings{hue} = $state->{hue};
$readings{sat} = $state->{sat};
$readings{bri} = $state->{bri}; $readings{bri} = $hash->{helper}{bri} if( !defined($readings{bri}) );
$readings{xy} = $state->{'xy'}->[0] .",". $state->{'xy'}->[1] if( defined($state->{'xy'}) );
$readings{colormode} = $state->{colormode};
$readings{alert} = $state->{alert};
$readings{effect} = $state->{effect};
$readings{reachable} = $state->{reachable}?1:0 if( defined($state->{reachable}) );
$readings{scene} = $state->{scene};
if( defined($readings{scene}) ) {
if( my $scenes = $hash->{helper}{scenes} ) {
for my $scene (@{$scenes}) {
if( $readings{scene} == $scene->{id} ) {
$readings{scene} = $scene->{name};
last;
}
}
}
}
my $s = '';
my $pct = -1;
my $on = $state->{on}; $readings{on} = $hash->{helper}{onoff} if( !defined($on) );
if( $on ) {
$s = 'on';
$readings{onoff} = 1;
if( !defined($readings{bri}) || AttrVal($name, 'subType', 'dimmer') eq 'switch' ) {
$pct = 100;
} else {
$pct = int($readings{bri} * 99 / 254 + 1);
if( $pct > 0
&& $pct < 100 ) {
$s = $dim_values{int($pct/7)};
}
$s = 'off' if( $pct == 0 );
}
} else {
$on = 0;
$s = 'off';
$pct = 0;
$readings{onoff} = 0;
}
$readings{pct} = $pct;
$s = 'unreachable' if( defined($readings{reachable}) && !$readings{reachable} );
#$readings{state} = $s;
}
}
readingsBeginUpdate($hash);
foreach my $key ( keys %readings ) {
if( defined($readings{$key}) ) {
readingsBulkUpdate($hash, $key, $readings{$key}, 1) if( !defined($hash->{helper}{$key}) || $hash->{helper}{$key} ne $readings{$key} );
$hash->{helper}{$key} = $readings{$key};
}
}
readingsEndUpdate($hash,1);
}
if( defined($hash->{helper}->{update}) ) {
delete $hash->{helper}->{update};
fhem( "set $hash->{IODev}{NAME} statusRequest" ) if( $hash->{IODev} );
return undef;
}
return undef;
}
$hash->{modelid} = $result->{modelid} if( defined($result->{modelid}) );
$attr{$name}{model} = $result->{modelid} if( !defined($attr{$name}{model}) && $result->{modelid} );
$hash->{productid} = $result->{productid} if( defined($result->{productid}) );
$hash->{swversion} = $result->{swversion} if( defined($result->{swversion}) );
$hash->{swconfigid} = $result->{swconfigid} if( defined($result->{swconfigid}) );
$hash->{manufacturername} = $result->{manufacturername} if( defined($result->{manufacturername}) );
$hash->{luminaireuniqueid} = $result->{luminaireuniqueid} if( defined($result->{luminaireuniqueid}) );
$hash->{power} = $result->{power} if( defined($result->{power}) );
if( $hash->{helper}->{devtype} eq 'S' ) {
my %readings;
if( my $config = $result->{config} ) {
$hash->{on} = $config->{on}?1:0 if( defined($config->{on}) );
$hash->{reachable} = $config->{reachable}?1:0 if( defined($config->{reachable}) );
$hash->{url} = $config->{url} if( defined($config->{url}) );
$hash->{lat} = $config->{lat} if( defined($config->{lat}) );
$hash->{long} = $config->{long} if( defined($config->{long}) );
$hash->{sunriseoffset} = $config->{sunriseoffset} if( defined($config->{sunriseoffset}) );
$hash->{sunsetoffset} = $config->{sunsetoffset} if( defined($config->{sunsetoffset}) );
$hash->{tholddark} = $config->{tholddark} if( defined($config->{tholddark}) );
$hash->{sensitivity} = $config->{sensitivity} if( defined($config->{sensitivity}) );
$readings{battery} = $config->{battery} if( defined($config->{battery}) );
$readings{reachable} = $config->{reachable} if( defined($config->{reachable}) );
$readings{temperature} = $config->{temperature} * 0.01 if( defined($config->{temperature}) );
#Xiaomi Aqara Vibrationsensor (lumi.vibration.aq1)
$hash->{sensitivitymax} = $config->{sensitivitymax} if( defined ($config->{sensitivitymax}) );
#Eurotronic Spirit ZigBee (SPZB0001)
$readings{heatsetpoint} = sprintf("%.1f",$config->{heatsetpoint} * 0.01) if( defined ($config->{heatsetpoint}) );
$readings{locked} = $config->{locked}?'true':'false' if( defined ($config->{locked}) );
$readings{displayflipped} = $config->{displayflipped}?'true':'false' if( defined ($config->{displayflipped}) );
$readings{mode} = $config->{mode} if( defined ($config->{mode}) );
}
my $lastupdated = '';
my $lastupdated_local = '';
my $offset = 0;
if( my $state = $result->{state} ) {
$lastupdated = $state->{lastupdated};
return undef if( !$lastupdated );
return undef if( $lastupdated eq 'none' );
substr( $lastupdated, 10, 1, ' ' ) if($lastupdated);
if( my $iohash = $hash->{IODev} ) {
substr( $lastupdated, 10, 1, '_' );
my $sec = SVG_time_to_sec($lastupdated);
$lastupdated = FmtDateTime($sec);
if( my $offset_bridge = $iohash->{helper}{offsetUTC} ) {
$offset = $offset_bridge;
Log3 $name, 4, "$name: use offsetUTC $offset from bridge";
}else{
#we do not have received the offsetUTC from the bridge, use the system offsetUTC until we received it
my @t = localtime(time);
$offset = timegm(@t) - timelocal(@t);
Log3 $name, 4, "$name: use offsetUTC $offset from system";
}
#add offset to UTC for displaying in fhem
$sec += $offset;
$lastupdated_local = FmtDateTime($sec);
}else{
$lastupdated_local = $lastupdated;
}
$readings{reachable} = ($state->{reachable}?1:0) if( defined($state->{reachable}) );
$readings{state} = $state->{status} if( defined($state->{status}) );
$readings{state} = $state->{flag}?'1':'0' if( defined($state->{flag}) );
$readings{state} = $state->{open}?'open':'closed' if( defined($state->{open}) );
$readings{state} = $state->{lightlevel} if( defined($state->{lightlevel}) && !defined($state->{lux}) );
$readings{state} = $state->{buttonevent} if( defined($state->{buttonevent}) );
$readings{state} = $state->{presence}?'motion':'nomotion' if( defined($state->{presence}) );
$readings{state} = $state->{fire}?'fire':'nofire' if( defined($state->{fire}) );
$readings{dark} = $state->{dark}?'1':'0' if( defined($state->{dark}) );
$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}) );
$readings{lightlevel} = $state->{lightlevel} if( defined($state->{lightlevel}) );
$readings{lux} = $state->{lux} if( defined($state->{lux}) );
$readings{power} = $state->{power} if( defined($state->{power}) );
$readings{voltage} = $state->{voltage} if( defined($state->{voltage}) );
$readings{current} = $state->{current} if( defined($state->{current}) );
$readings{consumption} = $state->{consumption} if( defined($state->{consumption}) );
$readings{water} = $state->{water} if( defined($state->{water}) );
$readings{fire} = $state->{fire} if( defined($state->{fire}) );
$readings{tampered} = $state->{tampered} if( defined($state->{tampered}) );
$readings{battery} = $state->{battery} if( defined($state->{battery}) );
$readings{batteryState} = $state->{lowbattery}?'low':'ok' if( defined($state->{lowbattery}) );
#Xiaomi Aqara Vibrationsensor (lumi.vibration.aq1)
$readings{tiltangle} = $state->{tiltangle} if( defined ($state->{tiltangle}) );
$readings{vibration} = $state->{vibration} if( defined ($state->{vibration}) );
$readings{orientation} = join(',', @{$state->{orientation}}) if( defined($state->{orientation}) && ref($state->{orientation}) eq 'ARRAY' );
$readings{vibrationstrength} = $state->{vibrationstrength} if( defined ($state->{vibrationstrength}) );
#Eurotronic Spirit ZigBee (SPZB0001)
$readings{valve} = ceil((100/255) * $state->{valve}) if( defined ($state->{valve}) );
#Heiman Gassensor HS1CG
$readings{carbonmonoxide} = $state->{carbonmonoxide} if( defined($state->{carbonmonoxide}) );
}
$hash->{lastupdated} = ReadingsVal( $name, '.lastupdated', '' ) if( !$hash->{lastupdated} );
$hash->{lastupdated_local} = ReadingsVal( $name, '.lastupdated_local', '' ) if( !$hash->{lastupdated_local} );
return undef if( $hash->{lastupdated}
&& $hash->{lastupdated} eq $lastupdated
&& (!$readings{state} || $readings{state} eq ReadingsVal( $name, 'state', '' )) );
Log3 $name, 4, "$name: lastupdated: $lastupdated, hash->{lastupdated}: $hash->{lastupdated}, lastupdated_local: $lastupdated_local, offsetUTC: $offset";
#Log3 $name, 5, "$name: ". Dumper $result if($HUEDevice_hasDataDumper);
$hash->{lastupdated} = $lastupdated;
$hash->{lastupdated_local} = $lastupdated_local;
if( scalar keys %readings ) {
readingsBeginUpdate($hash);
my $i = 0;
foreach my $key ( keys %readings ) {
if( defined($readings{$key}) ) {
if( $lastupdated_local) {
$hash->{'.updateTimestamp'} = $lastupdated_local;
$hash->{CHANGETIME}[$i] = $lastupdated_local;
}
readingsBulkUpdate($hash, $key, $readings{$key}, 1);
++$i;
}
}
if( $lastupdated_local ) {
$hash->{'.updateTimestamp'} = $lastupdated_local;
$hash->{CHANGETIME}[$i] = $lastupdated_local;
readingsBulkUpdate($hash, '.lastupdated_local', $lastupdated_local, 0);
}
if( $lastupdated ) {
readingsBulkUpdate($hash, '.lastupdated', $lastupdated, 0);
}
readingsEndUpdate($hash,1);
delete $hash->{CHANGETIME};
}
return undef;
}
if( !defined($attr{$name}{subType}) ) {
if( defined($attr{$name}{model}) ) {
if( defined($hueModels{$attr{$name}{model}}{subType}) ) {
$attr{$name}{subType} = $hueModels{$attr{$name}{model}}{subType};
HUEDeviceSetIcon($hash) if( $hash->{helper}{fromAutocreate} );
} elsif( $attr{$name}{model} =~ m/TW$/ ) {
$attr{$name}{subType} = 'ctdimmer';
} elsif( $attr{$name}{model} =~ m/RGB$/ ) {
$attr{$name}{subType} = 'colordimmer';
} elsif( $attr{$name}{model} =~ m/RGBW$/ ) {
$attr{$name}{subType} = 'extcolordimmer';
}
delete $hash->{helper}{fromAutocreate};
}
if( !defined($attr{$name}{subType}) && $hash->{type} ) {
if( $hash->{type} eq "Extended color light" ) {
$attr{$name}{subType} = 'extcolordimmer';
} elsif( $hash->{type} eq "Color light" ) {
$attr{$name}{subType} = 'colordimmer';
} elsif( $hash->{type} eq "Color temperature light" ) {
$attr{$name}{subType} = 'ctdimmer';
} elsif( $hash->{type} =~ m/Dimmable/ ) {
$attr{$name}{subType} = 'dimmer';
} elsif( $hash->{type} =~ m/On.Off/ ) {
$attr{$name}{subType} = 'switch';
}
}
} elsif( $attr{$name}{subType} eq "colordimmer" && defined($attr{$name}{model}) ) {
$attr{$name}{subType} = $hueModels{$attr{$name}{model}}{subType} if( defined($hueModels{$attr{$name}{model}}{subType}) );
}
$attr{$name}{devStateIcon} = '{(HUEDevice_devStateIcon($name),"toggle")}' if( !defined( $attr{$name}{devStateIcon} ) );
if( !defined($attr{$name}{webCmd}) && defined($attr{$name}{subType}) ) {
my $subtype = $attr{$name}{subType};
if( !$hash->{helper}->{devtype} ) {
$attr{$name}{webCmd} = 'rgb:rgb ff0000:rgb DEFF26:rgb 0000ff:ct 490:ct 380:ct 270:ct 160:toggle:on:off' if( $subtype eq "extcolordimmer" );
$attr{$name}{webCmd} = 'hue:rgb:rgb ff0000:rgb 98FF23:rgb 0000ff:toggle:on:off' if( $subtype eq "colordimmer" );
$attr{$name}{webCmd} = 'ct:ct 490:ct 380:ct 270:ct 160:toggle:on:off' if( $subtype eq "ctdimmer" );
$attr{$name}{webCmd} = 'pct:toggle:on:off' if( $subtype eq "dimmer" );
$attr{$name}{webCmd} = 'toggle:on:off' if( $subtype eq "switch" );
$attr{$name}{webCmd} = 'up:stop:down:pct' if( $subtype eq "blind" );
} elsif( $hash->{helper}->{devtype} eq 'G' ) {
$attr{$name}{webCmd} = 'on:off';
}
}
readingsBeginUpdate($hash);
my $state = $result->{'state'};
my $config = $result->{'config'};
my $on = $state->{on};
$on = $hash->{helper}{on} if( !defined($on) );
my $reachable = $state->{reachable}?1:0;
$reachable = $hash->{helper}{reachable} if( !defined($state->{reachable}) );
$reachable = 1 if( !$reachable && AttrVal($name, 'ignoreReachable', 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'};
my $xy = undef;
$xy = $state->{'xy'}->[0] .",". $state->{'xy'}->[1] if( defined($state->{'xy'}) );
my $alert = $state->{alert};
my $effect = $state->{effect};
my $rgb = undef;
$rgb = $state->{rgb} if( defined($state->{rgb}) );
my $battery = undef;
$battery = $config->{battery} if( defined($config->{battery}) );
my $mode = undef;
$mode = $state->{mode} if( defined($state->{mode}) && ($hash->{helper}{mode} || $state->{mode} ne 'homeautomation') );
if( defined($colormode) && $colormode ne $hash->{helper}{colormode} ) {readingsBulkUpdate($hash,"colormode",$colormode);}
if( defined($bri) && $bri != $hash->{helper}{bri} ) {readingsBulkUpdate($hash,"bri",$bri);}
if( defined($ct) && $ct != $hash->{helper}{ct} ) {
if( $ct == 0 ) {
readingsBulkUpdate($hash,"ct",$ct);
}
else {
readingsBulkUpdate($hash,"ct",$ct . " (".int(1000000/$ct)."K)");
}
}
if( defined($hue) && $hue != $hash->{helper}{hue} ) {readingsBulkUpdate($hash,"hue",$hue);}
if( defined($sat) && $sat != $hash->{helper}{sat} ) {readingsBulkUpdate($hash,"sat",$sat);}
if( defined($xy) && $xy ne $hash->{helper}{xy} ) {readingsBulkUpdate($hash,"xy",$xy);}
if( !defined($hash->{helper}{reachable}) || $reachable != $hash->{helper}{reachable} ) {readingsBulkUpdate($hash,"reachable",$reachable?1:0);}
if( defined($alert) && $alert ne $hash->{helper}{alert} ) {readingsBulkUpdate($hash,"alert",$alert);}
if( defined($effect) && $effect ne $hash->{helper}{effect} ) {readingsBulkUpdate($hash,"effect",$effect);}
if( defined($rgb) && $rgb ne $hash->{helper}{rgb} ) {readingsBulkUpdate($hash,"rgb",$rgb);}
if( defined($battery) && $battery ne $hash->{helper}{battery} ) {readingsBulkUpdate($hash,"battery",$battery);}
if( defined($mode) && $mode ne $hash->{helper}{mode} ) {readingsBulkUpdate($hash,"mode",$mode);}
my $s = '';
my $pct = -1;
if( defined($state->{'pct'}) ) {
$pct = $state->{'pct'};
$s = $pct;
} elsif( $on )
{
$s = 'on';
if( $on != $hash->{helper}{on} ) {readingsBulkUpdate($hash,"onoff",1);}
if( $bri < 0 || AttrVal($name, 'subType', 'dimmer') eq 'switch' ) {
$pct = 100;
} else {
$pct = int($bri * 99 / 254 + 1);
if( $pct > 0
&& $pct < 100 ) {
$s = $dim_values{int($pct/7)};
}
$s = 'off' if( $pct == 0 );
}
}
else
{
$on = 0;
$s = 'off';
$pct = 0;
if( $on != $hash->{helper}{on} ) {readingsBulkUpdate($hash,"onoff",0);}
}
if( $pct != $hash->{helper}{pct} ) {readingsBulkUpdate($hash,"pct", $pct);}
#if( $pct != $hash->{helper}{pct} ) {readingsBulkUpdate($hash,"level", $pct . ' %');}
$s = 'unreachable' if( !$reachable );
$hash->{helper}{on} = $on if( defined($on) );
$hash->{helper}{reachable} = $reachable if( defined($reachable) );
$hash->{helper}{colormode} = $colormode if( defined($colormode) );
$hash->{helper}{bri} = $bri if( defined($bri) );
$hash->{helper}{ct} = $ct if( defined($ct) );
$hash->{helper}{hue} = $hue if( defined($hue) );
$hash->{helper}{sat} = $sat if( defined($sat) );
$hash->{helper}{xy} = $xy if( defined($xy) );
$hash->{helper}{alert} = $alert if( defined($alert) );
$hash->{helper}{effect} = $effect if( defined($effect) );
$hash->{helper}{rgb} = $rgb if( defined($rgb) );
$hash->{helper}{battery} = $battery if( defined($battery) );
$hash->{helper}{mode} = $mode if( defined($mode) );
$hash->{helper}{pct} = $pct;
my $changed = $hash->{CHANGED}?1:0;
if( $s ne $hash->{STATE} ) {readingsBulkUpdate($hash,"state",$s);}
readingsEndUpdate($hash,1);
if( defined($colormode) && !defined($rgb) ) {
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);
return $changed;
}
sub
HUEDevice_Attr($$$;$)
{
my ($cmd, $name, $attrName, $attrVal) = @_;
if( $attrName eq 'setList' || $attrName eq 'configList' ) {
my $hash = $defs{$name};
delete $hash->{helper}{$attrName};
return "$name is not a sensor device" if( $hash->{helper}->{devtype} ne 'S' );
#return "$name is not a CLIP sensor device" if( $hash->{type} && $hash->{type} !~ m/^CLIP/ );
if( $cmd eq "set" && $attrVal ) {
foreach my $line ( split( "\n", $attrVal ) ) {
my($cmd,$json) = split( ":", $line,2 );
if( $cmd =~ m'^/(.*)/$' ) {
my $regex = $1;
$hash->{helper}{$attrName}{'regex'} = [] if( !$hash->{helper}{$attrName}{'regex'} );
push @{$hash->{helper}{$attrName}{'regex'}}, { regex => $regex, json => $json };
} else {
$hash->{helper}{$attrName}{cmds}{$cmd} = $json;
}
}
}
}
return;
}
1;
=pod
=item tag cloudfree
=item tag publicAPI
=item summary Devices connected to a Phillips HUE bridge, an LIGHTIFY or TRADFRI gateway
=item summary_DE Geräte an einer Philips HUE Bridge, einem LIGHTIFY oder Tradfri Gateway
=begin html
HUEDevice
Define
define <name> HUEDevice [group|sensor] <id> [<interval>]
Defines a device connected to a HUEBridge.
This can be a hue bulb, a living colors light or a living whites bulb or dimmer plug.
The device status will be updated every <interval> seconds. 0 means no updates.
The default and minimum is 60 if the IODev has not set pollDevices to 1.
The default ist 0 if the IODev has set pollDevices to 1.
Groups are updated only on definition and statusRequest
Examples:
define bulb HUEDevice 1
define LC HUEDevice 2
define allLights HUEDevice group 0
Readings
- bri
the brightness reported from the device. the value can be betwen 1 and 254
- colormode
the current colormode
- ct
the colortemperature in mireds and kelvin
- hue
the current hue
- pct
the current brightness in percent
- onoff
the current on/off state as 0 or 1
- sat
the current saturation
- xy
the current xy color coordinates
- state
the current state
Notes:
- with current bridge firware versions groups have
all_on
and any_on
readings,
with older firmware versions groups have no readings.
- not all readings show the actual device state. all readings not related to the current colormode have to be ignored.
- 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.
Set
- on [<ramp-time>]
- off [<ramp-time>]
- toggle [<ramp-time>]
- statusRequest
Request device status update.
- pct <value> [<ramp-time>]
dim to <value>
Note: the FS20 compatible dimXX% commands are also accepted.
- color <value>
set colortemperature to <value> kelvin.
- bri <value> [<ramp-time>]
set brighness to <value>; range is 0-254.
- dimUp [delta]
- dimDown [delta]
- ct <value> [<ramp-time>]
set colortemperature to <value> in mireds (range is 154-500) or kelvin (range is 2000-6493).
- ctUp [delta]
- ctDown [delta]
- hue <value> [<ramp-time>]
set hue to <value>; range is 0-65535.
- hueUp [delta]
- hueDown [delta]
- sat <value> [<ramp-time>]
set saturation to <value>; range is 0-254.
- satUp [delta]
- satDown [delta]
- xy <x>,<y> [<ramp-time>]
set the xy color coordinates to <x>,<y>
- alert [none|select|lselect]
- effect [none|colorloop]
- transitiontime <time>
set the transitiontime to <time> 1/10s
- rgb <rrggbb>
set the color to (the nearest equivalent of) <rrggbb>
- delayedUpdate
- immediateUpdate
- savescene <id>
- deletescene <id>
- scene
- lights <lights>
Only valid for groups. Changes the list of lights in this group.
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.
- json [setsensor|configsensor] <json>
send <json> to the state or config endpoints for this device.
- habridgeupdate [ : < on | off > ] [ : < bri | pct > < value > ]
This command is only for usage of HA-Bridges that are emulating an Hue Hub.
It updates your HA-Bridge internal light state of the devices without changing the devices itself.
bri and pct have to be used in the same way as changing the brightness or dimvalue of the device.
- set extensions are supported.
Note:
- <ramp-time> is given in seconds
- multiple paramters can be set at once separated by
:
Examples:
set LC on : transitiontime 100
set bulb on : bri 100 : color 4000
Get
- rgb
- RGB
- startup
show startup behavior.
- devStateIcon
returns html code that can be used to create an icon that represents the device color in the room overview.
Attributes
- color-icon
1 -> use lamp color as icon color and 100% shape as icon shape
2 -> use lamp color scaled to full brightness as icon color and dim state as icon shape
- createActionReadings
create readings for the last action in group devices
- createGroupReadings
create 'artificial' readings for group devices. default depends on the createGroupReadings setting in the bridge device.
- ignoreReachable
ignore the reachable state that is reported by the hue bridge. assume the device is allways reachable.
- setList
The list of know set commands for sensor type devices. one command per line, eg.:
attr mySensor setList present:{<json>}\
absent:{<json>}
- configList
The list of know config commands for sensor type devices. one command per line, eg.:
attr mySensor mode:{<json>}\
/heatsetpoint (.*)/:perl:{'{"heatsetpoint":'. $VALUE1 * 100 .'}'}
- subType
extcolordimmer -> device has rgb and color temperatur control
colordimmer -> device has rgb controll
ctdimmer -> device has color temperature control
dimmer -> device has brightnes controll
switch -> device has on/off controll
- transitiontime
default transitiontime for all set commands if not specified directly in the set.
- delayedUpdate
1 -> the update of the device status after a set command will be delayed for 1 second. usefull if multiple devices will be switched.
- devStateIcon
will be initialized to {(HUEDevice_devStateIcon($name),"toggle")}
to show device color as default in room overview.
- webCmd
will be initialized to a device specific value according to subType.
=end html
=encoding utf8
=for :application/json;q=META.json 31_HUEDevice.pm
{
"abstract": "devices connected to a Phillips HUE bridge, an Osram LIGHTIFY gateway or a IKEA TRADFRI gateway",
"x_lang": {
"de": {
"abstract": "Geräte an einer Philips HUE Bridge, einem Osram LIGHTIFY Gateway oder einem IKEA Tradfri Gateway"
}
},
"resources": {
"x_wiki": {
"web": "https://wiki.fhem.de/wiki/Hue"
}
},
"keywords": [
"fhem-mod",
"fhem-mod-device",
"HUE",
"zigbee"
],
"release_status": "stable",
"x_fhem_maintainer": [
"justme1968"
],
"x_fhem_maintainer_github": [
"justme-1968"
],
"prereqs": {
"runtime": {
"requires": {
"FHEM": 5.00918799,
"perl": 5.014,
"Meta": 0,
"Color": 0,
"SetExtensions": 0,
"JSON": 0,
"Time::Local": 0
},
"recommends": {
},
"suggests": {
"HUEBridge": 0,
"tradfri": 0,
"LIGHTIFY": 0
}
}
}
}
=end :application/json;q=META.json
=cut