# $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