| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047 |
- # $Id: 30_LIGHTIFY.pm 13672 2017-03-11 23:20:19Z justme1968 $
- package main;
- use strict;
- use warnings;
- use Color;
- use JSON;
- use IO::Socket::INET;
- use IO::File;
- use IO::Handle;
- use Data::Dumper;
- use constant { getDevices => '13', # 19
- getGroups => '1E', # 30
- addToGroup => '20', # 32
- removeFromGroup => '21', # 33
- getGroupInfo => '26', # 38
- setGroupName => '27', # 39
- setName => '28', # 40
- setDim => '31', # 49
- setOnOff => '32', # 50
- setCT => '33', # 51
- setRGB => '36', # 54
- setPhysical => '38',
- goToScene => '52', # 82
- getStatus => '68', # 104
- setSoftOn => 'DB', # -37
- setSoftOff => 'DC', # -36
- switch => 0x1,
- ctdimmer => 0x2,
- dimmer => 0x4,
- colordimmer => 0x8,
- extcolordimmer => 0x10,
- motiondetector => 0x20,
- pushbuton => 0x41,
- };
- sub
- LIGHTIFY_Initialize($)
- {
- my ($hash) = @_;
- $hash->{ReadFn} = "LIGHTIFY_Read";
- $hash->{WriteFn} = "LIGHTIFY_Write";
- $hash->{Clients} = ":HUEDevice:";
- $hash->{DefFn} = "LIGHTIFY_Define";
- $hash->{NOTIFYDEV} = "global";
- $hash->{NotifyFn} = "LIGHTIFY_Notify";
- $hash->{UndefFn} = "LIGHTIFY_Undefine";
- $hash->{SetFn} = "LIGHTIFY_Set";
- #$hash->{GetFn} = "LIGHTIFY_Get";
- $hash->{AttrFn} = "LIGHTIFY_Attr";
- $hash->{AttrList} = "disable:1,0 disabledForIntervals pollDevices:1";
- }
- #####################################
- sub
- LIGHTIFY_Define($$)
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- return "Usage: define <name> LIGHTIFY host" if(@a < 3);
- my $name = $a[0];
- my $host = $a[2];
- $hash->{NAME} = $name;
- $hash->{Host} = $host;
- $hash->{INTERVAL} = 60;
- if( $init_done ) {
- LIGHTIFY_Disconnect($hash);
- LIGHTIFY_Connect($hash);
- } elsif( $hash->{STATE} ne "???" ) {
- readingsSingleUpdate($hash, 'state', 'initialized', 1 );
- }
- $attr{$name}{pollDevices} = 1;
- return undef;
- }
- sub
- LIGHTIFY_Notify($$)
- {
- my ($hash,$dev) = @_;
- return if($dev->{NAME} ne "global");
- return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
- LIGHTIFY_Connect($hash);
- return undef;
- }
- sub
- LIGHTIFY_Connect($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- return undef if( IsDisabled($name) > 0 );
- $hash->{MSG_NR} = 0;
- my @send_queue = ();
- $hash->{SEND_QUEUE} = \@send_queue;
- $hash->{UNCONFIRMED} = 0;
- $hash->{PARTIAL} = "";
- my $socket = IO::Socket::INET->new( PeerAddr => $hash->{Host},
- PeerPort => 4000, #AttrVal($name, "port", 4000),
- Timeout => 4,
- );
- if($socket) {
- readingsSingleUpdate($hash, 'state', 'connected', 1 );
- $hash->{LAST_CONNECT} = FmtDateTime( gettimeofday() );
- $hash->{FD} = $socket->fileno();
- $hash->{CD} = $socket; # sysread / close won't work on fileno
- $hash->{CONNECTS}++;
- $selectlist{$name} = $hash;
- Log3 $name, 3, "$name: connected to $hash->{Host}";
- LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- LIGHTIFY_sendRaw( $hash, '00', getGroups ." 00 00 00 00 00" );
- } else {
- Log3 $name, 3, "$name: failed to connect to $hash->{Host}";
- LIGHTIFY_Disconnect($hash);
- InternalTimer(gettimeofday()+10, "LIGHTIFY_Connect", $hash, 0);
- }
- }
- sub
- LIGHTIFY_Disconnect($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- RemoveInternalTimer($hash);
- return if( !$hash->{CD} );
- close($hash->{CD}) if($hash->{CD});
- delete($hash->{FD});
- delete($hash->{CD});
- delete($selectlist{$name});
- readingsSingleUpdate($hash, 'state', 'disconnected', 1 );
- Log3 $name, 3, "$name: Disconnected";
- $hash->{LAST_DISCONNECT} = FmtDateTime( gettimeofday() );
- }
- sub
- LIGHTIFY_Undefine($$)
- {
- my ($hash, $arg) = @_;
- LIGHTIFY_Disconnect($hash);
- return undef;
- }
- sub
- LIGHTIFY_sendRaw($$$;$)
- {
- my ($hash, $flag, $hex, $force) = @_;
- my $name = $hash->{NAME};
- return undef if( IsDisabled($name) > 0 );
- return "not connected" if( !$hash->{CD} );
- if( !$force && $hash->{UNCONFIRMED} ) {
- for(my $i = int(@{$hash->{SEND_QUEUE}}); $i >= 0; --$i) {
- my $a = $hash->{SEND_QUEUE}[$i];
- next if( !$a );
- if( $flag eq $a->[0] && $hex eq $a->[1] ) {
- Log3 $name, 4, "$name: discard: $flag, $hex";
- if( 1 ) {
- splice @{$hash->{SEND_QUEUE}}, $i, 1;
- } else {
- return undef;
- }
- }
- }
- Log3 $name, 4, "$name: enque: $flag, $hex";
- push @{$hash->{SEND_QUEUE}}, [$flag, $hex, $hash->{CL}];
- return undef;
- }
- substr($hex,2*1,2+1,sprintf( '%02X', $hash->{MSG_NR} ) );
- $hash->{MSG_NR}++;
- $hash->{MSG_NR} &= 0xFF;
- $hex =~ s/ //g;
- my $length = length($hex)/2+1;
- $hex = sprintf( '%02X%02X', $length & 0xff, $length >> 8 ) . $flag . $hex;
- Log3 $name, 4, "$name: sending: ". $hex;
- #return undef if( IsDisabled($name) > 0 );
- #return "not connected" if( !$hash->{CD} );
- syswrite($hash->{CD}, pack('H*', $hex));
- $hash->{helper}{CL} = $hash->{CL};
- $hash->{UNCONFIRMED}++ if( !$force );
- RemoveInternalTimer($hash, "LIGHTIFY_sendNext");
- InternalTimer(gettimeofday()+1, "LIGHTIFY_sendNext", $hash, 0);
- return undef;
- }
- sub
- LIGHTIFY_Write($@)
- {
- my ($hash,$chash,$name,$id,$obj)= @_;
- #Log 3, Dumper $obj;
- return undef if( !$chash );
- my $flag = '00';
- my $light = $chash->{ID};
- if( $chash->{helper}->{devtype} && $chash->{helper}->{devtype} eq 'G' ) {
- my $group = $chash->{ID};
- $group =~ s/^.//;
- $group = sprintf( "%02X", $group );
- if( $group eq '00' ) {
- $light = 'FF FF FF FF FF FF FF FF';
- } else {
- $flag = '02';
- $light = "$group 00 00 00 00 00 00 00";
- if( $obj ) {
- if( defined($obj->{name}) ) {
- my $name;
- for( my $i = 0; $i < 15; ++$i ) {
- $name .= sprintf( "%02X ", ord(substr($obj->{name},$i,1)) );
- }
- $name .= '00';
- LIGHTIFY_sendRaw( $hash, $flag, setGroupName ." 00 00 00 00 $group 00 $name" );
- CommandAttr(undef,"$chash->{NAME} alias $obj->{name}");
- CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
- #LIGHTIFY_sendRaw( $hash, '00', getGroups ." 00 00 00 00 00" );
- return undef;
- }
- }
- }
- $chash->{helper}{on} = -1;
- }
- if( $obj ) {
- if( defined($obj->{name}) ) {
- my $name;
- for( my $i = 0; $i < 15; ++$i ) {
- $name .= sprintf( "%02X ", ord(substr($obj->{name},$i,1)) );
- }
- $name .= '00';
- LIGHTIFY_sendRaw( $hash, $flag, setName ." 00 00 00 00 $light $name" );
- CommandAttr(undef,"$chash->{NAME} alias $obj->{name}");
- CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
- #LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- return undef;
- }
- }
- my $force = ($chash->{helper}->{update_timeout} && $chash->{helper}->{update_timeout} == -1);
- my $transitiontime = 2;
- my $json = { state => { xreachable => 1, } };
- if( $obj ) {
- if( defined($obj->{on}) ) {
- my $onoff = "00";
- $onoff = "01" if( $obj->{on} );
- LIGHTIFY_sendRaw( $hash, $flag, setOnOff ." 00 00 00 00 $light $onoff", $force ) if( 1 || $obj->{on} != $chash->{helper}{on} );
- $json->{state}{on} = $obj->{on} ? JSON::true : JSON::false;
- }
- if( defined($obj->{ct}) ) {
- my $ct = int(1000000 / $obj->{ct});
- $ct = sprintf( '%02X%02X', $ct & 0xff, $ct >> 8 );
- $transitiontime = $obj->{transitiontime} if( defined($obj->{transitiontime}) );
- my $t = sprintf( '%02X%02X', $transitiontime & 0xff, $transitiontime >> 8 );
- LIGHTIFY_sendRaw( $hash, $flag, setCT ." 00 00 00 00 $light $ct $t", $force );
- $json->{state}{colormode} = 'ct';
- $json->{state}{ct} = $obj->{ct};
- } elsif( defined($obj->{hue}) || defined($obj->{sat}) ) {
- my $hue = ReadingsVal($chash->{NAME}, 'hue', 65535 );
- my $sat = ReadingsVal($chash->{NAME}, 'sat', 254 );
- my $bri = ReadingsVal($chash->{NAME}, 'bri', 254 );
- $hue = $obj->{hue} if( defined($obj->{hue}) );
- $sat = $obj->{sat} if( defined($obj->{sat}) );
- $bri = $obj->{bri} if( defined($obj->{bri}) );
- $json->{state}{colormode} = 'hs';
- $json->{state}{bri} = $obj->{bri};
- $json->{state}{hue} = $obj->{hue};
- $json->{state}{sat} = $obj->{sat};
- my $h = $hue / 65535.0;
- my $s = $sat / 254.0;
- my $v = $bri / 254.0;
- my ($r,$g,$b) = Color::hsv2rgb($h,$s,$v);
- $r *= 255;
- $g *= 255;
- $b *= 255;
- my $rgb = sprintf( "%02X%02X%02X", $r+0.5, $g+0.5, $b+0.5 );
- $transitiontime = $obj->{transitiontime} if( defined($obj->{transitiontime}) );
- my $t = sprintf( '%02X%02X', $transitiontime & 0xff, $transitiontime >> 8 );
- LIGHTIFY_sendRaw( $hash, $flag, setRGB ." 00 00 00 00 $light $rgb 00 $t", $force );
- }
- if( defined($obj->{bri})
- && !defined($obj->{hue}) && !defined($obj->{sat}) ) {
- my $bri = $obj->{bri};
- $bri /= 2.54;
- $bri = sprintf( "%02X", $bri );
- $transitiontime = $obj->{transitiontime} if( defined($obj->{transitiontime}) );
- my $t = sprintf( '%02X%02X', $transitiontime & 0xff, $transitiontime >> 8 );
- LIGHTIFY_sendRaw( $hash, $flag, setDim ." 00 00 00 00 $light $bri $t", $force );
- $json->{state}{bri} = $obj->{bri};
- }
- }
- my $fake = (0 && $chash->{helper}->{update_timeout} && $chash->{helper}->{update_timeout} != 0);
- if( $obj && $fake ) {
- HUEDevice_Parse( $chash, $json );
- $chash->{helper}->{update_timeout} = AttrVal($name, "delayedUpdate", 0);;
- #$chash->{helper}->{update_timeout} = 1 if( !$chash->{helper}->{update_timeout} );
- RemoveInternalTimer($chash);
- InternalTimer(gettimeofday()+$chash->{helper}->{update_timeout}, "HUEDevice_GetUpdate", $chash, 0);
- } else {
- if( $flag eq '00' && $light ne 'FF FF FF FF FF FF FF FF' ) {
- LIGHTIFY_sendRaw( $hash, '00', getStatus ." 00 00 00 00 $light" );
- $chash->{helper}{transitiontime} = int($transitiontime/10) if( $obj );
- #RemoveInternalTimer($chash);
- #InternalTimer(gettimeofday()+5, "HUEDevice_GetUpdate", $chash, 0);
- } else {
- LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- }
- }
- my %ret = ();
- return \%ret;
- }
- sub
- LIGHTIFY_Set($$@)
- {
- my ($hash, $name, $cmd, @args) = @_;
- $hash->{".triggerUsed"} = 1;
- my $list = "";
- $list .= "on off " if( $hash->{CD} );
- $list .= "raw " if( $hash->{CD} );
- $list .= "reconnect:noArg ";
- $list .= "goToScene " if( $hash->{CD} );
- $list .= "setRGBW " if( $hash->{CD} );
- $list .= "setSoftOn setSoftOff " if( $hash->{CD} );
- $list .= "statusRequest:noArg " if( $hash->{CD} );
- if( $cmd eq 'on' ) {
- LIGHTIFY_sendRaw( $hash, '00', setOnOff ." 00 00 00 00 FF FF FF FF FF FF FF FF 01", 1 );
- LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- return undef;
- } elsif( $cmd eq 'off' ) {
- LIGHTIFY_sendRaw( $hash, '00', setOnOff ." 00 00 00 00 FF FF FF FF FF FF FF FF 00", 1 );
- LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- return undef;
- } elsif( $cmd eq 'raw' ) {
- return LIGHTIFY_sendRaw( '00', $hash, join( '', @args ) );
- return undef;
- } elsif( $cmd eq 'received' ) {
- my $hex = join( '', @args );
- $hex =~ s/ //g;
- LIGHTIFY_Parse($hash, $hex);
- return undef;
- } elsif( $cmd eq 'reconnect' ) {
- delete $hash->{CL};
- LIGHTIFY_Disconnect($hash);
- LIGHTIFY_Connect($hash);
- return undef;
- } elsif( $cmd eq 'statusRequest' ) {
- return LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- } elsif( $cmd eq 'getGroups' ) {
- return LIGHTIFY_sendRaw( $hash, '00', getGroups ." 00 00 00 00 00" );
- } elsif( $cmd eq 'getGroupInfo' ) {
- return "usage: getGroupInfo <groupId>" if( !$args[0] );
- return "usage: <groupId> musst be numeric" if( $args[0] !~ /^\d*$/ );
- return "usage: <groupId> musst be in the range [0-255]" if( $args[0] < 0 || $args[0] > 255 );
- my $group = $args[0];
- $group = 1 if( !$group || $group < 1 );
- $group = sprintf( "%02i", $group );
- return LIGHTIFY_sendRaw( $hash, '00', getGroupInfo ." 00 00 00 00 $group 00" );
- } elsif( $cmd eq 'addToGroup' ) {
- return "usage: addToGroup <groupId> <addr> <name>" if( !$args[2] );
- return "usage: <groupId> musst be numeric" if( $args[0] !~ /^\d*$/ );
- return "usage: <groupId> musst be in the range [0-255]" if( $args[0] < 0 || $args[0] > 255 );
- return "usage: <addr> musst be a 16 hex digit device address" if( $args[1] !~ /^[A-F0-9]{16}$/i );
- my $group = $args[0];
- $group = 1 if( !$group || $group < 1 );
- $group = sprintf( "%02i", $group );
- my $new = join( ' ', @args[2..@args-1]);
- my $name;
- for( my $i = 0; $i < 15; ++$i ) {
- $name .= sprintf( "%02X ", ord(substr($new,$i,1)) );
- }
- my $length = sprintf( "%02X ", length($name) );
- LIGHTIFY_sendRaw( $hash, '02', addToGroup ." 00 00 00 00 $group 00 $args[1] $length $name" );
- LIGHTIFY_sendRaw( $hash, '00', getGroups ." 00 00 00 00 00" );
- return undef;
- } elsif( $cmd eq 'removeFromGroup' ) {
- return "usage: removeFromGroup <groupId> <addr>" if( !$args[1] );
- return "usage: <groupId> musst be numeric" if( $args[0] !~ /^\d*$/ );
- return "usage: <groupId> musst be in the range [0-255]" if( $args[0] < 0 || $args[0] > 255 );
- return "usage: <addr> musst be a 16 hex digit device address" if( $args[1] !~ /^[A-F0-9]{16}$/i );
- my $group = $args[0];
- $group = 1 if( !$group || $group < 1 );
- $group = sprintf( "%02i", $group );
- return LIGHTIFY_sendRaw( $hash, '02', removeFromGroup ." 00 00 00 00 $group 00 $args[1]" );
- } elsif( $cmd eq 'getStatus' ) {
- return "usage: getStatus <addr>" if( !$args[0] );
- return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
- return LIGHTIFY_sendRaw( $hash, '00', getStatus ." 00 00 00 00 $args[0]" );
- } elsif( $cmd eq 'goToScene' ) {
- return "usage: goToScene <sceneId>" if( !$args[0] );
- return "usage: <sceneId> musst be numeric" if( $args[0] !~ /^\d*$/ );
- my $scene = $args[0];
- $scene = 1 if( !$scene || $scene < 1 );
- $scene = sprintf( "%02i", $scene );
- LIGHTIFY_sendRaw( $hash, '00', goToScene ." 00 00 00 00 $scene" );
- return LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- } elsif( $cmd eq 'saveScene' ) {
- return "usage: saveScene <sceneId>" if( !$args[0] );
- return "usage: <sceneId> musst be numeric" if( $args[0] !~ /^\d*$/ );
- my $scene = $args[0];
- $scene = 1 if( !$scene || $scene < 1 );
- $scene = sprintf( "%02i", $scene );
- return LIGHTIFY_sendRaw( $hash, '02', goToScene ." 00 00 00 00 $scene" );
- return LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- } elsif( $cmd eq 'setSoftOn' ) {
- return "usage: setSoftOn <addr> <transitiontime>" if( !defined($args[1]) );
- return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
- return "usage: <transitiontime> musst be numeric" if( $args[1] !~ /^\d*$/ );
- return "usage: <transitiontime> musst be in the range [0-255]" if( $args[1] < 0 || $args[1] > 255 );
- my $transitiontime = sprintf( '%02X', $args[1] & 0xff );
- return LIGHTIFY_sendRaw( $hash, '00', setSoftOn ." 00 00 00 00 $args[0] $transitiontime" );
- } elsif( $cmd eq 'setSoftOff' ) {
- return "usage: setSoftOff <addr> <transitiontime>" if( !defined($args[1]) );
- return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
- return "usage: <transitiontime> musst be numeric" if( $args[1] !~ /^\d*$/ );
- return "usage: <transitiontime> musst be in the range [0-255]" if( $args[1] < 0 || $args[1] > 255 );
- my $transitiontime = sprintf( '%02X', $args[1] & 0xff );
- return LIGHTIFY_sendRaw( $hash, '00', setSoftOff ." 00 00 00 00 $args[0] $transitiontime" );
- } elsif( $cmd eq 'setPhysical' ) {
- return "usage: setPhysical <addr>" if( !defined($args[0]) );
- return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
- return LIGHTIFY_sendRaw( $hash, '00', setPhysical ." 00 00 00 00 $args[0] 00" );
- } elsif( $cmd eq 'setRGBW' ) {
- return "usage: setRGBW <addr> <RRGGBBWW>" if( !defined($args[1]) );
- return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
- return "usage: <RRGGBBWW> musst be a 8 hex digits rgbw color" if( $args[1] !~ /^[A-F0-9]{8}$/i );
- return LIGHTIFY_sendRaw( $hash, '00', setRGB ." 00 00 00 00 $args[0] $args[1] 0200" );
- }
- return "Unknown argument $cmd, choose one of $list";
- }
- sub
- LIGHTIFY_poll($)
- {
- my ($hash) = @_;
- RemoveInternalTimer($hash, "LIGHTIFY_poll");
- LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
- }
- sub
- LIGHTIFY_Get($$@)
- {
- my ($hash, $name, $cmd) = @_;
- my $list = "";
- return "Unknown argument $cmd, choose one of $list";
- }
- sub
- LIGHTIFY_Attr($$$)
- {
- my ($cmd, $name, $attrName, $attrVal) = @_;
- my $orig = $attrVal;
- $attrVal = int($attrVal) if($attrName eq "interval");
- $attrVal = 60 if($attrName eq "interval" && $attrVal < 60 && $attrVal != 0);
- if( $attrName eq "disable" ) {
- my $hash = $defs{$name};
- if( $cmd eq 'set' && $attrVal ne "0" ) {
- LIGHTIFY_Disconnect($hash);
- } else {
- $attr{$name}{$attrName} = 0;
- LIGHTIFY_Disconnect($hash);
- LIGHTIFY_Connect($hash);
- }
- }
- if( $cmd eq 'set' ) {
- if( $orig ne $attrVal ) {
- $attr{$name}{$attrName} = $attrVal;
- return $attrName ." set to ". $attrVal;
- }
- }
- return;
- }
- sub
- LIGHTIFY_sendNext($)
- {
- my ($hash) = @_;
- $hash->{UNCONFIRMED}-- if( $hash->{UNCONFIRMED} > 0 );
- if( $hash->{SEND_QUEUE} ) {
- my $a = shift @{$hash->{SEND_QUEUE}};
- if( $a ) {
- $hash->{CL} = $a->[2];
- LIGHTIFY_sendRaw( $hash, $a->[0], $a->[1] ) if( $a );
- delete $hash->{CL};
- }
- }
- }
- sub
- LIGHTIFY_toJson($$$$$$$$$$)
- {
- my ($hash,$chash,$id,$reachable,$onoff,$dim,$ct,$r,$g,$b) = @_;
- my $json = { state => { } };
- if( $chash ) {
- $json->{uniqueid} = $id if( defined($id) );
- $json->{state}{on} = $onoff if( defined($onoff) );
- $json->{state}{reachable} = $reachable? 1 : 0 if( defined($reachable) );
- if( !$chash->{helper}{type} ) {
- Log3 $hash->{NAME}, 2, "$chash->{NAME}: unknown light type";
- } elsif( $chash->{helper}{type} == motiondetector ) {
- $json->{type} = 'MotionDetector';
- } elsif( $chash->{helper}{type} == extcolordimmer ) {
- $json->{type} = 'Extended color light';
- } elsif( $chash->{helper}{type} == colordimmer ) {
- $json->{type} = 'Color light';
- } elsif( $chash->{helper}{type} == ctdimmer ) {
- $json->{type} = 'Color temperature tight';
- } elsif( $chash->{helper}{type} == dimmer ) {
- $json->{type} = 'Dimmable';
- } else {
- $json->{type} = 'On/Off';
- }
- my $has_ct = ($chash->{helper}{type} & 0x02) ? 1: 0;
- my $has_rgb = ($chash->{helper}{type} & 0x08) ? 1 : 0;
- my $is_sensor = ($chash->{helper}{type} >= 0x20) ? 1 : 0;
- if( $is_sensor ) {
- $json->{state}->{lastupdated} = TimeNow();
- if( $chash->{helper}{type} == motiondetector ) {
- if( $r eq '01' ) {
- $json->{config}->{on} = 1;
- $json->{state}->{presence} = $g eq '01'?1:0;
- } else {
- $json->{config}->{on} = 0;
- }
- }
- } elsif( $has_rgb ) {
- if( $has_ct && "$r$g$b" eq '111' ) {
- $json->{state}->{colormode} = 'ct';
- } elsif( defined($r) ) {
- my( $r, $g, $b ) = (hex($r)/255.0, hex($g)/255.0, hex($b)/255.0);
- my( $h, $s, $v ) = Color::rgb2hsv($r,$g,$b);
- $json->{state}{colormode} = 'hs';
- $json->{state}{hue} = int( $h * 65535 ),
- $json->{state}{sat} = int( $s * 254 ),
- $json->{state}{bri} = int( $v * 254 ),
- }
- } elsif( $has_ct && $ct ) {
- $json->{state}->{colormode} = 'ct';
- } else {
- }
- $json->{state}{ct} = int(1000000/$ct) if( $ct );
- $json->{state}{bri} = int($dim/100*254) if( defined($dim) );
- }
- return $json;
- }
- sub
- LIGHTIFY_Parse($$)
- {
- my ($hash,$hex) = @_;
- my $name = $hash->{NAME};
- $hex = uc($hex);
- Log3 $name, 4, "$name: parsing: $hex";
- my $length = hex(substr($hex,2*1,2*1).substr($hex,2*0,2*1));
- my $flag = substr($hex,2*2,2*1);
- my $cmd = substr($hex,2*3,2*1);
- my $cnt = substr($hex,2*4,2*1);
- my $err = substr($hex,2*8,2*1);
- if( $err ne '00' ) {
- readingsSingleUpdate($hash, 'lastError', "for cmd: $cmd: err: $err", 0 );
- Log3 $name, 3, "$name: got error: $err ";
- return undef;
- }
- if( $cmd eq getDevices ) {
- my $nr_lights = hex(substr($hex,2*10,2*1).substr($hex,2*9,2*1));
- return undef if( !$nr_lights );
- my $offset = ($length+2-11) / $nr_lights;
- Log3 $name, 2, "$name: warning: offset for cmd $cmd is $offset instead of 50" if( $offset != 50 );
- my $autocreated = 0;
- for( my $i = 0; $i < $nr_lights; ++$i ) {
- my $short = substr($hex,$i*$offset*2+2*11,2*2);
- my $id = substr($hex,$i*$offset*2+2*13,2*8);
- my $type = substr($hex,$i*$offset*2+2*21,2*1);
- my $firmware = substr($hex,$i*$offset*2+2*22,2*4);
- my $reachable = hex(substr($hex,$i*$offset*2+2*26,2*1));
- my $groups = (substr($hex,$i*$offset*2+2*28,2*1).substr($hex,$i*$offset*2+2*27,2*1));
- my $onoff = hex(substr($hex,$i*$offset*2+2*29,2*1));
- my $dim = hex(substr($hex,$i*$offset*2+2*30,2*1));
- my $ct = hex(substr($hex,$i*$offset*2+2*32,2*1).substr($hex,$i*$offset*2+2*31,2*1));
- my $r = substr($hex,$i*$offset*2+2*33,2*1);
- my $g = substr($hex,$i*$offset*2+2*34,2*1);
- my $b = substr($hex,$i*$offset*2+2*35,2*1);
- my $w = substr($hex,$i*$offset*2+2*36,2*1);
- my $alias = pack('H*', substr($hex,$i*$offset*2+2*37,2*15));
- $alias =~ s/\x00//g;
- #my $count1 = substr($hex,$i*$offset*2+2*53,2*4); #reportMissingCount
- #my $count2 = substr($hex,$i*$offset*2+2*57,2*4); #pollingCount
- #Log 1, "count1: $count1, count2, $count2";
- my $has_ct = (hex($type) & 0x02) ? 1: 0;
- my $has_rgb = (hex($type) & 0x08) ? 1 : 0;
- my $is_sensor = (hex($type) >= 0x20) ? 1 : 0;
- #$has_ct = 1 if( $type eq '00' );
- Log3 $name, 4, "$alias: $id:$short, type: $type (ct:$has_ct, rgb:$has_rgb, sensor:$is_sensor), firmware: $firmware, reachable: $reachable, groups: $groups, onoff: $onoff, dim: $dim, ct: $ct, rgb: $r$g$b, w: $w";
- #my $code = $id;
- my $code = $name ."-". $id;
- $code = $name ."-S". $id if( $is_sensor );
- if( defined($modules{HUEDevice}{defptr}{$code}) ) {
- Log3 $name, 5, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'";
- } else {
- my $devname = "LIGHTIFY" . $id;
- #my $define= "$devname HUEDevice $id";
- my $define= "$devname HUEDevice $id IODev=$name";
- $define= "$devname HUEDevice sensor $id IODev=$name" if( $is_sensor );
- Log3 $name, 4, "$name: create new device '$devname' for address '$id'";
- my $cmdret= CommandDefine(undef,$define);
- if($cmdret) {
- Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
- } else {
- $cmdret = CommandAttr(undef,"$devname alias ".$alias);
- $cmdret = CommandAttr(undef,"$devname room LIGHTIFY");
- $cmdret = CommandAttr(undef,"$devname IODev $name");
- $autocreated++;
- }
- }
- if( my $chash = $modules{HUEDevice}{defptr}{$code} ) {
- $chash->{helper}{type} = hex($type);
- $chash->{helper}{type} = extcolordimmer if( !$chash->{helper}{type} );
- my $json = LIGHTIFY_toJson($hash, $chash, $id, $reachable, $onoff, $dim, $ct, $r, $g, $b);
- my $changed = HUEDevice_Parse( $chash, $json );
- if( $changed || $chash->{helper}{transitiontime} ) {
- RemoveInternalTimer($chash);
- InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0);
- $chash->{helper}{transitiontime} -= 1 if( $chash->{helper}{transitiontime} );
- }
- }
- }
- if( $autocreated ) {
- Log3 $name, 2, "$name: autocreated $autocreated devices";
- CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
- }
- RemoveInternalTimer($hash, "LIGHTIFY_poll");
- InternalTimer(gettimeofday()+$hash->{INTERVAL}, "LIGHTIFY_poll", $hash, 0);
- } elsif( $cmd eq getGroups ) {
- my $nr_groups = hex(substr($hex,2*10,2*1).substr($hex,2*9,2*1));
- #Log 1, unpack 'v', pack 'H*', substr($hex,2*9,2*2);
- return undef if( !$nr_groups );
- my $offset = ($length+2-11) / $nr_groups;
- Log3 $name, 2, "$name: warning: offset for cmd $cmd is $offset instead of 18" if( $offset != 18 );
- my @groups;
- my $autocreated = 0;
- for( my $i = 0; $i <= $nr_groups; ++$i ) {
- my $id;
- my $alias;
- if( $i == 0 ) {
- $id = 0;
- $alias = 'Gruppe alles';
- } else {
- $id = hex(substr($hex,($i-1)*$offset*2+2*12,2*1).substr($hex,($i-1)*$offset*2+2*11,2*1));
- $alias = pack('H*', substr($hex,($i-1)*$offset*2+2*13,2*15));
- $alias =~ s/\x00//g;
- my $group = sprintf( "%02X", $id );
- $hash->{CL} = $hash->{helper}{CL};
- LIGHTIFY_sendRaw( $hash, '00', getGroupInfo ." 00 00 00 00 $group 00" );
- delete $hash->{CL};
- }
- push @groups, "$id: $alias";
- #my $code = $id;
- my $code = $name ."-G". $id;
- if( defined($modules{HUEDevice}{defptr}{$code}) ) {
- Log3 $name, 5, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'";
- } else {
- my $devname = "LIGHTIFYGroup" . $id;
- my $define= "$devname HUEDevice group $id IODev=$name";
- Log3 $name, 4, "$name: create new device '$devname' for group nr. '$id'";
- my $cmdret= CommandDefine(undef,$define);
- if($cmdret) {
- Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
- } else {
- $cmdret = CommandAttr(undef,"$devname alias ".$alias);
- $cmdret = CommandAttr(undef,"$devname room LIGHTIFY");
- $cmdret = CommandAttr(undef,"$devname IODev $name");
- $cmdret = CommandAttr(undef,"$devname subType switch") if( $id == 0 );
- $autocreated++;
- }
- }
- }
- Log3 $name, 4, "groups: " .join( ', ', @groups );
- if( $autocreated ) {
- Log3 $name, 2, "$name: autocreated $autocreated groups";
- CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
- }
- asyncOutput( $hash->{helper}{CL}, "got groups: ". join( ', ', @groups ) ) if( $hash->{helper}{CL} );
- } elsif( $cmd eq getGroupInfo ) {
- my $nr = hex(substr($hex,2*10,2*1).substr($hex,2*9,2*1));
- my $alias = pack('H*', substr($hex,2*11,2*15));
- my $nr_lights = hex(substr($hex,2*27,2*1));
- return undef if( !$nr_lights );
- $alias =~ s/\x00//g;
- my $offset = ($length+2-28) / $nr_lights; # should be 8
- Log3 $name, 2, "$name: warning: offset for cmd $cmd is $offset instead of 8" if( $offset != 8 );
- my @lights;
- for( my $i = 0; $i < $nr_lights; ++$i ) {
- my $light = substr($hex,$i*$offset*2+2*28,2*8);
- push @lights, $light;
- }
- Log3 $name, 4, "group $nr: alias: $alias, lights: " .join( ',', @lights );
- my $code = $name ."-G". $nr;
- if( my $chash = $modules{HUEDevice}{defptr}{$code} ) {
- $chash->{lights} = join( ',', @lights );
- }
- asyncOutput( $hash->{helper}{CL}, "group info: $nr: $alias, lights: ". join( ', ', @lights ) ) if( $hash->{helper}{CL} );
- } elsif( $cmd eq setOnOff ) {
- my $id = substr($hex,2*11,2*8);
- my $onoff = hex(substr($hex,2*19,2*1));
- } elsif( $cmd eq getStatus ) {
- my $id = substr($hex,2*11,2*8);
- my $json;
- my $code = $name ."-". $id;
- my $chash = $modules{HUEDevice}{defptr}{$code};
- if( !$chash ) {
- $code = $name ."-S". $id;
- $chash = $modules{HUEDevice}{defptr}{$code};
- }
- if( $length < 30 ) {
- $json = { state => { } };
- if( substr($hex,2*19,2*1) eq 'FF' ) {
- Log3 $name, 4, "$id, not reachable";
- $json = { state => { reachable => 0 } };
- }
- } else {
- my $reachable = hex(substr($hex,2*20,2*1));
- my $onoff = hex(substr($hex,2*21,2*1));
- my $dim = hex(substr($hex,2*22,2*1));
- my $ct = hex(substr($hex,2*24,2*1).substr($hex,2*23,2*1));
- my $r = substr($hex,2*25,2*1);
- my $g = substr($hex,2*26,2*1);
- my $b = substr($hex,2*27,2*1);
- my $w = substr($hex,2*28,2*1);
- Log3 $name, 4, "$id, reachable: $reachable, onoff: $onoff, dim: $dim, ct: $ct, rgb: $r$g$b, w: $w";
- $json = LIGHTIFY_toJson($hash, $chash, $id, $reachable, $onoff, $dim, $ct, $r, $g, $b);
- }
- my $changed = HUEDevice_Parse( $chash, $json ) if( $chash );
- if( $changed || $chash->{helper}{transitiontime} ) {
- RemoveInternalTimer($chash);
- InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0);
- $chash->{helper}{transitiontime} -= 1 if( $chash->{helper}{transitiontime} );
- }
- } else {
- Log3 $name, 4, "$name: unhandled message $hex ";
- }
- }
- sub
- LIGHTIFY_Read($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $buf;
- my $ret = sysread($hash->{CD}, $buf, 1024);
- if( !defined($ret) || !$ret ) {
- Log3 $name, 4, "$name: disconnected";
- LIGHTIFY_Disconnect($hash);
- InternalTimer(gettimeofday()+10, "LIGHTIFY_Connect", $hash, 0);
- return;
- }
- my $hex = unpack('H*', $buf);
- Log3 $name, 5, "$name: received: $hex";
- $hash->{PARTIAL} .= $hex;
- my $length = hex(substr($hash->{PARTIAL},2*1,2*1).substr($hash->{PARTIAL},2*0,2*1));
- while( $hash->{PARTIAL} && $length+2 <= length($hash->{PARTIAL})/2 ) {
- $hex = substr($hash->{PARTIAL},0,$length*2+2*2);
- $hash->{PARTIAL} = substr($hash->{PARTIAL},$length*2+2*2);
- $length = hex(substr($hash->{PARTIAL},2*1,2*1).substr($hash->{PARTIAL},2*0,2*1)) if( $hash->{PARTIAL} );
- LIGHTIFY_Parse($hash, $hex);
- }
- readingsSingleUpdate($hash, 'state', $hash->{READINGS}{state}{VAL}, 0);
- LIGHTIFY_sendNext( $hash ) if( !$hash->{PARTIAL} );
- #RemoveInternalTimer($hash);
- #InternalTimer(gettimeofday()+2, "LIGHTIFY_sendNext", $hash, 0);
- }
- 1;
- =pod
- =item summary module for the osram lightify gateway
- =item summary_DE Modul für das Osram LIGHTFY Gateway
- =begin html
- <a name="LIGHTIFY"></a>
- <h3>LIGHTIFY</h3>
- <ul>
- Module to integrate a OSRAM LIGHTIFY gateway into FHEM;.<br><br>
- The actual LIGHTIFY lights are defined as <a href="#HUEDevice">HUEDevice</a> devices.
- <br><br>
- All newly found devices and groups are autocreated at startup and added to the room LIGHTIFY.
- <br><br>
- Notes:
- <ul>
- <li>Autocreate only works for the first gateway. Devices on other gateways have to be manualy defined.</li>
- </ul>
- <br><br>
- <a name="LIGTHIFY_Define"></a>
- <b>Define</b>
- <ul>
- <code>define <name> LIGHTIFY <host></code><br>
- <br>
- Defines a LIGHTIFY gateway device with address <host>.<br><br>
- Examples:
- <ul>
- <code>define gateway LIGHTIFY 10.0.1.100</code><br>
- </ul>
- </ul><br>
- <a name="LIGHTIFY_Get"></a>
- <b>Get</b>
- <ul>
- </ul><br>
- <a name="LIGHTIFY_Set"></a>
- <b>Set</b>
- <ul>
- <li>on</li>
- <li>off</li>
- <li>goToScene <sceneId></li>
- <li>setSoftOn <addr> <transitiontime></li>
- <li>setSoftOff <addr> <transitiontime></li>
- <li>reconnect<br>
- Closes and reopens the connection to the gateway.</li>
- <li>statusRequest<br>
- Update light status.</li>
- </ul><br>
- <a name="LIGHTIFY_Attr"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#disable">disable</a></li>
- <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
- </ul><br>
- </ul><br>
- =end html
- =cut
|