30_LIGHTIFY.pm 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  1. # $Id: 30_LIGHTIFY.pm 13672 2017-03-11 23:20:19Z justme1968 $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use Color;
  6. use JSON;
  7. use IO::Socket::INET;
  8. use IO::File;
  9. use IO::Handle;
  10. use Data::Dumper;
  11. use constant { getDevices => '13', # 19
  12. getGroups => '1E', # 30
  13. addToGroup => '20', # 32
  14. removeFromGroup => '21', # 33
  15. getGroupInfo => '26', # 38
  16. setGroupName => '27', # 39
  17. setName => '28', # 40
  18. setDim => '31', # 49
  19. setOnOff => '32', # 50
  20. setCT => '33', # 51
  21. setRGB => '36', # 54
  22. setPhysical => '38',
  23. goToScene => '52', # 82
  24. getStatus => '68', # 104
  25. setSoftOn => 'DB', # -37
  26. setSoftOff => 'DC', # -36
  27. switch => 0x1,
  28. ctdimmer => 0x2,
  29. dimmer => 0x4,
  30. colordimmer => 0x8,
  31. extcolordimmer => 0x10,
  32. motiondetector => 0x20,
  33. pushbuton => 0x41,
  34. };
  35. sub
  36. LIGHTIFY_Initialize($)
  37. {
  38. my ($hash) = @_;
  39. $hash->{ReadFn} = "LIGHTIFY_Read";
  40. $hash->{WriteFn} = "LIGHTIFY_Write";
  41. $hash->{Clients} = ":HUEDevice:";
  42. $hash->{DefFn} = "LIGHTIFY_Define";
  43. $hash->{NOTIFYDEV} = "global";
  44. $hash->{NotifyFn} = "LIGHTIFY_Notify";
  45. $hash->{UndefFn} = "LIGHTIFY_Undefine";
  46. $hash->{SetFn} = "LIGHTIFY_Set";
  47. #$hash->{GetFn} = "LIGHTIFY_Get";
  48. $hash->{AttrFn} = "LIGHTIFY_Attr";
  49. $hash->{AttrList} = "disable:1,0 disabledForIntervals pollDevices:1";
  50. }
  51. #####################################
  52. sub
  53. LIGHTIFY_Define($$)
  54. {
  55. my ($hash, $def) = @_;
  56. my @a = split("[ \t][ \t]*", $def);
  57. return "Usage: define <name> LIGHTIFY host" if(@a < 3);
  58. my $name = $a[0];
  59. my $host = $a[2];
  60. $hash->{NAME} = $name;
  61. $hash->{Host} = $host;
  62. $hash->{INTERVAL} = 60;
  63. if( $init_done ) {
  64. LIGHTIFY_Disconnect($hash);
  65. LIGHTIFY_Connect($hash);
  66. } elsif( $hash->{STATE} ne "???" ) {
  67. readingsSingleUpdate($hash, 'state', 'initialized', 1 );
  68. }
  69. $attr{$name}{pollDevices} = 1;
  70. return undef;
  71. }
  72. sub
  73. LIGHTIFY_Notify($$)
  74. {
  75. my ($hash,$dev) = @_;
  76. return if($dev->{NAME} ne "global");
  77. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  78. LIGHTIFY_Connect($hash);
  79. return undef;
  80. }
  81. sub
  82. LIGHTIFY_Connect($)
  83. {
  84. my ($hash) = @_;
  85. my $name = $hash->{NAME};
  86. return undef if( IsDisabled($name) > 0 );
  87. $hash->{MSG_NR} = 0;
  88. my @send_queue = ();
  89. $hash->{SEND_QUEUE} = \@send_queue;
  90. $hash->{UNCONFIRMED} = 0;
  91. $hash->{PARTIAL} = "";
  92. my $socket = IO::Socket::INET->new( PeerAddr => $hash->{Host},
  93. PeerPort => 4000, #AttrVal($name, "port", 4000),
  94. Timeout => 4,
  95. );
  96. if($socket) {
  97. readingsSingleUpdate($hash, 'state', 'connected', 1 );
  98. $hash->{LAST_CONNECT} = FmtDateTime( gettimeofday() );
  99. $hash->{FD} = $socket->fileno();
  100. $hash->{CD} = $socket; # sysread / close won't work on fileno
  101. $hash->{CONNECTS}++;
  102. $selectlist{$name} = $hash;
  103. Log3 $name, 3, "$name: connected to $hash->{Host}";
  104. LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  105. LIGHTIFY_sendRaw( $hash, '00', getGroups ." 00 00 00 00 00" );
  106. } else {
  107. Log3 $name, 3, "$name: failed to connect to $hash->{Host}";
  108. LIGHTIFY_Disconnect($hash);
  109. InternalTimer(gettimeofday()+10, "LIGHTIFY_Connect", $hash, 0);
  110. }
  111. }
  112. sub
  113. LIGHTIFY_Disconnect($)
  114. {
  115. my ($hash) = @_;
  116. my $name = $hash->{NAME};
  117. RemoveInternalTimer($hash);
  118. return if( !$hash->{CD} );
  119. close($hash->{CD}) if($hash->{CD});
  120. delete($hash->{FD});
  121. delete($hash->{CD});
  122. delete($selectlist{$name});
  123. readingsSingleUpdate($hash, 'state', 'disconnected', 1 );
  124. Log3 $name, 3, "$name: Disconnected";
  125. $hash->{LAST_DISCONNECT} = FmtDateTime( gettimeofday() );
  126. }
  127. sub
  128. LIGHTIFY_Undefine($$)
  129. {
  130. my ($hash, $arg) = @_;
  131. LIGHTIFY_Disconnect($hash);
  132. return undef;
  133. }
  134. sub
  135. LIGHTIFY_sendRaw($$$;$)
  136. {
  137. my ($hash, $flag, $hex, $force) = @_;
  138. my $name = $hash->{NAME};
  139. return undef if( IsDisabled($name) > 0 );
  140. return "not connected" if( !$hash->{CD} );
  141. if( !$force && $hash->{UNCONFIRMED} ) {
  142. for(my $i = int(@{$hash->{SEND_QUEUE}}); $i >= 0; --$i) {
  143. my $a = $hash->{SEND_QUEUE}[$i];
  144. next if( !$a );
  145. if( $flag eq $a->[0] && $hex eq $a->[1] ) {
  146. Log3 $name, 4, "$name: discard: $flag, $hex";
  147. if( 1 ) {
  148. splice @{$hash->{SEND_QUEUE}}, $i, 1;
  149. } else {
  150. return undef;
  151. }
  152. }
  153. }
  154. Log3 $name, 4, "$name: enque: $flag, $hex";
  155. push @{$hash->{SEND_QUEUE}}, [$flag, $hex, $hash->{CL}];
  156. return undef;
  157. }
  158. substr($hex,2*1,2+1,sprintf( '%02X', $hash->{MSG_NR} ) );
  159. $hash->{MSG_NR}++;
  160. $hash->{MSG_NR} &= 0xFF;
  161. $hex =~ s/ //g;
  162. my $length = length($hex)/2+1;
  163. $hex = sprintf( '%02X%02X', $length & 0xff, $length >> 8 ) . $flag . $hex;
  164. Log3 $name, 4, "$name: sending: ". $hex;
  165. #return undef if( IsDisabled($name) > 0 );
  166. #return "not connected" if( !$hash->{CD} );
  167. syswrite($hash->{CD}, pack('H*', $hex));
  168. $hash->{helper}{CL} = $hash->{CL};
  169. $hash->{UNCONFIRMED}++ if( !$force );
  170. RemoveInternalTimer($hash, "LIGHTIFY_sendNext");
  171. InternalTimer(gettimeofday()+1, "LIGHTIFY_sendNext", $hash, 0);
  172. return undef;
  173. }
  174. sub
  175. LIGHTIFY_Write($@)
  176. {
  177. my ($hash,$chash,$name,$id,$obj)= @_;
  178. #Log 3, Dumper $obj;
  179. return undef if( !$chash );
  180. my $flag = '00';
  181. my $light = $chash->{ID};
  182. if( $chash->{helper}->{devtype} && $chash->{helper}->{devtype} eq 'G' ) {
  183. my $group = $chash->{ID};
  184. $group =~ s/^.//;
  185. $group = sprintf( "%02X", $group );
  186. if( $group eq '00' ) {
  187. $light = 'FF FF FF FF FF FF FF FF';
  188. } else {
  189. $flag = '02';
  190. $light = "$group 00 00 00 00 00 00 00";
  191. if( $obj ) {
  192. if( defined($obj->{name}) ) {
  193. my $name;
  194. for( my $i = 0; $i < 15; ++$i ) {
  195. $name .= sprintf( "%02X ", ord(substr($obj->{name},$i,1)) );
  196. }
  197. $name .= '00';
  198. LIGHTIFY_sendRaw( $hash, $flag, setGroupName ." 00 00 00 00 $group 00 $name" );
  199. CommandAttr(undef,"$chash->{NAME} alias $obj->{name}");
  200. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  201. #LIGHTIFY_sendRaw( $hash, '00', getGroups ." 00 00 00 00 00" );
  202. return undef;
  203. }
  204. }
  205. }
  206. $chash->{helper}{on} = -1;
  207. }
  208. if( $obj ) {
  209. if( defined($obj->{name}) ) {
  210. my $name;
  211. for( my $i = 0; $i < 15; ++$i ) {
  212. $name .= sprintf( "%02X ", ord(substr($obj->{name},$i,1)) );
  213. }
  214. $name .= '00';
  215. LIGHTIFY_sendRaw( $hash, $flag, setName ." 00 00 00 00 $light $name" );
  216. CommandAttr(undef,"$chash->{NAME} alias $obj->{name}");
  217. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  218. #LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  219. return undef;
  220. }
  221. }
  222. my $force = ($chash->{helper}->{update_timeout} && $chash->{helper}->{update_timeout} == -1);
  223. my $transitiontime = 2;
  224. my $json = { state => { xreachable => 1, } };
  225. if( $obj ) {
  226. if( defined($obj->{on}) ) {
  227. my $onoff = "00";
  228. $onoff = "01" if( $obj->{on} );
  229. LIGHTIFY_sendRaw( $hash, $flag, setOnOff ." 00 00 00 00 $light $onoff", $force ) if( 1 || $obj->{on} != $chash->{helper}{on} );
  230. $json->{state}{on} = $obj->{on} ? JSON::true : JSON::false;
  231. }
  232. if( defined($obj->{ct}) ) {
  233. my $ct = int(1000000 / $obj->{ct});
  234. $ct = sprintf( '%02X%02X', $ct & 0xff, $ct >> 8 );
  235. $transitiontime = $obj->{transitiontime} if( defined($obj->{transitiontime}) );
  236. my $t = sprintf( '%02X%02X', $transitiontime & 0xff, $transitiontime >> 8 );
  237. LIGHTIFY_sendRaw( $hash, $flag, setCT ." 00 00 00 00 $light $ct $t", $force );
  238. $json->{state}{colormode} = 'ct';
  239. $json->{state}{ct} = $obj->{ct};
  240. } elsif( defined($obj->{hue}) || defined($obj->{sat}) ) {
  241. my $hue = ReadingsVal($chash->{NAME}, 'hue', 65535 );
  242. my $sat = ReadingsVal($chash->{NAME}, 'sat', 254 );
  243. my $bri = ReadingsVal($chash->{NAME}, 'bri', 254 );
  244. $hue = $obj->{hue} if( defined($obj->{hue}) );
  245. $sat = $obj->{sat} if( defined($obj->{sat}) );
  246. $bri = $obj->{bri} if( defined($obj->{bri}) );
  247. $json->{state}{colormode} = 'hs';
  248. $json->{state}{bri} = $obj->{bri};
  249. $json->{state}{hue} = $obj->{hue};
  250. $json->{state}{sat} = $obj->{sat};
  251. my $h = $hue / 65535.0;
  252. my $s = $sat / 254.0;
  253. my $v = $bri / 254.0;
  254. my ($r,$g,$b) = Color::hsv2rgb($h,$s,$v);
  255. $r *= 255;
  256. $g *= 255;
  257. $b *= 255;
  258. my $rgb = sprintf( "%02X%02X%02X", $r+0.5, $g+0.5, $b+0.5 );
  259. $transitiontime = $obj->{transitiontime} if( defined($obj->{transitiontime}) );
  260. my $t = sprintf( '%02X%02X', $transitiontime & 0xff, $transitiontime >> 8 );
  261. LIGHTIFY_sendRaw( $hash, $flag, setRGB ." 00 00 00 00 $light $rgb 00 $t", $force );
  262. }
  263. if( defined($obj->{bri})
  264. && !defined($obj->{hue}) && !defined($obj->{sat}) ) {
  265. my $bri = $obj->{bri};
  266. $bri /= 2.54;
  267. $bri = sprintf( "%02X", $bri );
  268. $transitiontime = $obj->{transitiontime} if( defined($obj->{transitiontime}) );
  269. my $t = sprintf( '%02X%02X', $transitiontime & 0xff, $transitiontime >> 8 );
  270. LIGHTIFY_sendRaw( $hash, $flag, setDim ." 00 00 00 00 $light $bri $t", $force );
  271. $json->{state}{bri} = $obj->{bri};
  272. }
  273. }
  274. my $fake = (0 && $chash->{helper}->{update_timeout} && $chash->{helper}->{update_timeout} != 0);
  275. if( $obj && $fake ) {
  276. HUEDevice_Parse( $chash, $json );
  277. $chash->{helper}->{update_timeout} = AttrVal($name, "delayedUpdate", 0);;
  278. #$chash->{helper}->{update_timeout} = 1 if( !$chash->{helper}->{update_timeout} );
  279. RemoveInternalTimer($chash);
  280. InternalTimer(gettimeofday()+$chash->{helper}->{update_timeout}, "HUEDevice_GetUpdate", $chash, 0);
  281. } else {
  282. if( $flag eq '00' && $light ne 'FF FF FF FF FF FF FF FF' ) {
  283. LIGHTIFY_sendRaw( $hash, '00', getStatus ." 00 00 00 00 $light" );
  284. $chash->{helper}{transitiontime} = int($transitiontime/10) if( $obj );
  285. #RemoveInternalTimer($chash);
  286. #InternalTimer(gettimeofday()+5, "HUEDevice_GetUpdate", $chash, 0);
  287. } else {
  288. LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  289. }
  290. }
  291. my %ret = ();
  292. return \%ret;
  293. }
  294. sub
  295. LIGHTIFY_Set($$@)
  296. {
  297. my ($hash, $name, $cmd, @args) = @_;
  298. $hash->{".triggerUsed"} = 1;
  299. my $list = "";
  300. $list .= "on off " if( $hash->{CD} );
  301. $list .= "raw " if( $hash->{CD} );
  302. $list .= "reconnect:noArg ";
  303. $list .= "goToScene " if( $hash->{CD} );
  304. $list .= "setRGBW " if( $hash->{CD} );
  305. $list .= "setSoftOn setSoftOff " if( $hash->{CD} );
  306. $list .= "statusRequest:noArg " if( $hash->{CD} );
  307. if( $cmd eq 'on' ) {
  308. LIGHTIFY_sendRaw( $hash, '00', setOnOff ." 00 00 00 00 FF FF FF FF FF FF FF FF 01", 1 );
  309. LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  310. return undef;
  311. } elsif( $cmd eq 'off' ) {
  312. LIGHTIFY_sendRaw( $hash, '00', setOnOff ." 00 00 00 00 FF FF FF FF FF FF FF FF 00", 1 );
  313. LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  314. return undef;
  315. } elsif( $cmd eq 'raw' ) {
  316. return LIGHTIFY_sendRaw( '00', $hash, join( '', @args ) );
  317. return undef;
  318. } elsif( $cmd eq 'received' ) {
  319. my $hex = join( '', @args );
  320. $hex =~ s/ //g;
  321. LIGHTIFY_Parse($hash, $hex);
  322. return undef;
  323. } elsif( $cmd eq 'reconnect' ) {
  324. delete $hash->{CL};
  325. LIGHTIFY_Disconnect($hash);
  326. LIGHTIFY_Connect($hash);
  327. return undef;
  328. } elsif( $cmd eq 'statusRequest' ) {
  329. return LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  330. } elsif( $cmd eq 'getGroups' ) {
  331. return LIGHTIFY_sendRaw( $hash, '00', getGroups ." 00 00 00 00 00" );
  332. } elsif( $cmd eq 'getGroupInfo' ) {
  333. return "usage: getGroupInfo <groupId>" if( !$args[0] );
  334. return "usage: <groupId> musst be numeric" if( $args[0] !~ /^\d*$/ );
  335. return "usage: <groupId> musst be in the range [0-255]" if( $args[0] < 0 || $args[0] > 255 );
  336. my $group = $args[0];
  337. $group = 1 if( !$group || $group < 1 );
  338. $group = sprintf( "%02i", $group );
  339. return LIGHTIFY_sendRaw( $hash, '00', getGroupInfo ." 00 00 00 00 $group 00" );
  340. } elsif( $cmd eq 'addToGroup' ) {
  341. return "usage: addToGroup <groupId> <addr> <name>" if( !$args[2] );
  342. return "usage: <groupId> musst be numeric" if( $args[0] !~ /^\d*$/ );
  343. return "usage: <groupId> musst be in the range [0-255]" if( $args[0] < 0 || $args[0] > 255 );
  344. return "usage: <addr> musst be a 16 hex digit device address" if( $args[1] !~ /^[A-F0-9]{16}$/i );
  345. my $group = $args[0];
  346. $group = 1 if( !$group || $group < 1 );
  347. $group = sprintf( "%02i", $group );
  348. my $new = join( ' ', @args[2..@args-1]);
  349. my $name;
  350. for( my $i = 0; $i < 15; ++$i ) {
  351. $name .= sprintf( "%02X ", ord(substr($new,$i,1)) );
  352. }
  353. my $length = sprintf( "%02X ", length($name) );
  354. LIGHTIFY_sendRaw( $hash, '02', addToGroup ." 00 00 00 00 $group 00 $args[1] $length $name" );
  355. LIGHTIFY_sendRaw( $hash, '00', getGroups ." 00 00 00 00 00" );
  356. return undef;
  357. } elsif( $cmd eq 'removeFromGroup' ) {
  358. return "usage: removeFromGroup <groupId> <addr>" if( !$args[1] );
  359. return "usage: <groupId> musst be numeric" if( $args[0] !~ /^\d*$/ );
  360. return "usage: <groupId> musst be in the range [0-255]" if( $args[0] < 0 || $args[0] > 255 );
  361. return "usage: <addr> musst be a 16 hex digit device address" if( $args[1] !~ /^[A-F0-9]{16}$/i );
  362. my $group = $args[0];
  363. $group = 1 if( !$group || $group < 1 );
  364. $group = sprintf( "%02i", $group );
  365. return LIGHTIFY_sendRaw( $hash, '02', removeFromGroup ." 00 00 00 00 $group 00 $args[1]" );
  366. } elsif( $cmd eq 'getStatus' ) {
  367. return "usage: getStatus <addr>" if( !$args[0] );
  368. return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
  369. return LIGHTIFY_sendRaw( $hash, '00', getStatus ." 00 00 00 00 $args[0]" );
  370. } elsif( $cmd eq 'goToScene' ) {
  371. return "usage: goToScene <sceneId>" if( !$args[0] );
  372. return "usage: <sceneId> musst be numeric" if( $args[0] !~ /^\d*$/ );
  373. my $scene = $args[0];
  374. $scene = 1 if( !$scene || $scene < 1 );
  375. $scene = sprintf( "%02i", $scene );
  376. LIGHTIFY_sendRaw( $hash, '00', goToScene ." 00 00 00 00 $scene" );
  377. return LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  378. } elsif( $cmd eq 'saveScene' ) {
  379. return "usage: saveScene <sceneId>" if( !$args[0] );
  380. return "usage: <sceneId> musst be numeric" if( $args[0] !~ /^\d*$/ );
  381. my $scene = $args[0];
  382. $scene = 1 if( !$scene || $scene < 1 );
  383. $scene = sprintf( "%02i", $scene );
  384. return LIGHTIFY_sendRaw( $hash, '02', goToScene ." 00 00 00 00 $scene" );
  385. return LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  386. } elsif( $cmd eq 'setSoftOn' ) {
  387. return "usage: setSoftOn <addr> <transitiontime>" if( !defined($args[1]) );
  388. return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
  389. return "usage: <transitiontime> musst be numeric" if( $args[1] !~ /^\d*$/ );
  390. return "usage: <transitiontime> musst be in the range [0-255]" if( $args[1] < 0 || $args[1] > 255 );
  391. my $transitiontime = sprintf( '%02X', $args[1] & 0xff );
  392. return LIGHTIFY_sendRaw( $hash, '00', setSoftOn ." 00 00 00 00 $args[0] $transitiontime" );
  393. } elsif( $cmd eq 'setSoftOff' ) {
  394. return "usage: setSoftOff <addr> <transitiontime>" if( !defined($args[1]) );
  395. return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
  396. return "usage: <transitiontime> musst be numeric" if( $args[1] !~ /^\d*$/ );
  397. return "usage: <transitiontime> musst be in the range [0-255]" if( $args[1] < 0 || $args[1] > 255 );
  398. my $transitiontime = sprintf( '%02X', $args[1] & 0xff );
  399. return LIGHTIFY_sendRaw( $hash, '00', setSoftOff ." 00 00 00 00 $args[0] $transitiontime" );
  400. } elsif( $cmd eq 'setPhysical' ) {
  401. return "usage: setPhysical <addr>" if( !defined($args[0]) );
  402. return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
  403. return LIGHTIFY_sendRaw( $hash, '00', setPhysical ." 00 00 00 00 $args[0] 00" );
  404. } elsif( $cmd eq 'setRGBW' ) {
  405. return "usage: setRGBW <addr> <RRGGBBWW>" if( !defined($args[1]) );
  406. return "usage: <addr> musst be a 16 hex digit device address" if( $args[0] !~ /^[A-F0-9]{16}$/i );
  407. return "usage: <RRGGBBWW> musst be a 8 hex digits rgbw color" if( $args[1] !~ /^[A-F0-9]{8}$/i );
  408. return LIGHTIFY_sendRaw( $hash, '00', setRGB ." 00 00 00 00 $args[0] $args[1] 0200" );
  409. }
  410. return "Unknown argument $cmd, choose one of $list";
  411. }
  412. sub
  413. LIGHTIFY_poll($)
  414. {
  415. my ($hash) = @_;
  416. RemoveInternalTimer($hash, "LIGHTIFY_poll");
  417. LIGHTIFY_sendRaw( $hash, '00', getDevices ." 00 00 00 00 01" );
  418. }
  419. sub
  420. LIGHTIFY_Get($$@)
  421. {
  422. my ($hash, $name, $cmd) = @_;
  423. my $list = "";
  424. return "Unknown argument $cmd, choose one of $list";
  425. }
  426. sub
  427. LIGHTIFY_Attr($$$)
  428. {
  429. my ($cmd, $name, $attrName, $attrVal) = @_;
  430. my $orig = $attrVal;
  431. $attrVal = int($attrVal) if($attrName eq "interval");
  432. $attrVal = 60 if($attrName eq "interval" && $attrVal < 60 && $attrVal != 0);
  433. if( $attrName eq "disable" ) {
  434. my $hash = $defs{$name};
  435. if( $cmd eq 'set' && $attrVal ne "0" ) {
  436. LIGHTIFY_Disconnect($hash);
  437. } else {
  438. $attr{$name}{$attrName} = 0;
  439. LIGHTIFY_Disconnect($hash);
  440. LIGHTIFY_Connect($hash);
  441. }
  442. }
  443. if( $cmd eq 'set' ) {
  444. if( $orig ne $attrVal ) {
  445. $attr{$name}{$attrName} = $attrVal;
  446. return $attrName ." set to ". $attrVal;
  447. }
  448. }
  449. return;
  450. }
  451. sub
  452. LIGHTIFY_sendNext($)
  453. {
  454. my ($hash) = @_;
  455. $hash->{UNCONFIRMED}-- if( $hash->{UNCONFIRMED} > 0 );
  456. if( $hash->{SEND_QUEUE} ) {
  457. my $a = shift @{$hash->{SEND_QUEUE}};
  458. if( $a ) {
  459. $hash->{CL} = $a->[2];
  460. LIGHTIFY_sendRaw( $hash, $a->[0], $a->[1] ) if( $a );
  461. delete $hash->{CL};
  462. }
  463. }
  464. }
  465. sub
  466. LIGHTIFY_toJson($$$$$$$$$$)
  467. {
  468. my ($hash,$chash,$id,$reachable,$onoff,$dim,$ct,$r,$g,$b) = @_;
  469. my $json = { state => { } };
  470. if( $chash ) {
  471. $json->{uniqueid} = $id if( defined($id) );
  472. $json->{state}{on} = $onoff if( defined($onoff) );
  473. $json->{state}{reachable} = $reachable? 1 : 0 if( defined($reachable) );
  474. if( !$chash->{helper}{type} ) {
  475. Log3 $hash->{NAME}, 2, "$chash->{NAME}: unknown light type";
  476. } elsif( $chash->{helper}{type} == motiondetector ) {
  477. $json->{type} = 'MotionDetector';
  478. } elsif( $chash->{helper}{type} == extcolordimmer ) {
  479. $json->{type} = 'Extended color light';
  480. } elsif( $chash->{helper}{type} == colordimmer ) {
  481. $json->{type} = 'Color light';
  482. } elsif( $chash->{helper}{type} == ctdimmer ) {
  483. $json->{type} = 'Color temperature tight';
  484. } elsif( $chash->{helper}{type} == dimmer ) {
  485. $json->{type} = 'Dimmable';
  486. } else {
  487. $json->{type} = 'On/Off';
  488. }
  489. my $has_ct = ($chash->{helper}{type} & 0x02) ? 1: 0;
  490. my $has_rgb = ($chash->{helper}{type} & 0x08) ? 1 : 0;
  491. my $is_sensor = ($chash->{helper}{type} >= 0x20) ? 1 : 0;
  492. if( $is_sensor ) {
  493. $json->{state}->{lastupdated} = TimeNow();
  494. if( $chash->{helper}{type} == motiondetector ) {
  495. if( $r eq '01' ) {
  496. $json->{config}->{on} = 1;
  497. $json->{state}->{presence} = $g eq '01'?1:0;
  498. } else {
  499. $json->{config}->{on} = 0;
  500. }
  501. }
  502. } elsif( $has_rgb ) {
  503. if( $has_ct && "$r$g$b" eq '111' ) {
  504. $json->{state}->{colormode} = 'ct';
  505. } elsif( defined($r) ) {
  506. my( $r, $g, $b ) = (hex($r)/255.0, hex($g)/255.0, hex($b)/255.0);
  507. my( $h, $s, $v ) = Color::rgb2hsv($r,$g,$b);
  508. $json->{state}{colormode} = 'hs';
  509. $json->{state}{hue} = int( $h * 65535 ),
  510. $json->{state}{sat} = int( $s * 254 ),
  511. $json->{state}{bri} = int( $v * 254 ),
  512. }
  513. } elsif( $has_ct && $ct ) {
  514. $json->{state}->{colormode} = 'ct';
  515. } else {
  516. }
  517. $json->{state}{ct} = int(1000000/$ct) if( $ct );
  518. $json->{state}{bri} = int($dim/100*254) if( defined($dim) );
  519. }
  520. return $json;
  521. }
  522. sub
  523. LIGHTIFY_Parse($$)
  524. {
  525. my ($hash,$hex) = @_;
  526. my $name = $hash->{NAME};
  527. $hex = uc($hex);
  528. Log3 $name, 4, "$name: parsing: $hex";
  529. my $length = hex(substr($hex,2*1,2*1).substr($hex,2*0,2*1));
  530. my $flag = substr($hex,2*2,2*1);
  531. my $cmd = substr($hex,2*3,2*1);
  532. my $cnt = substr($hex,2*4,2*1);
  533. my $err = substr($hex,2*8,2*1);
  534. if( $err ne '00' ) {
  535. readingsSingleUpdate($hash, 'lastError', "for cmd: $cmd: err: $err", 0 );
  536. Log3 $name, 3, "$name: got error: $err ";
  537. return undef;
  538. }
  539. if( $cmd eq getDevices ) {
  540. my $nr_lights = hex(substr($hex,2*10,2*1).substr($hex,2*9,2*1));
  541. return undef if( !$nr_lights );
  542. my $offset = ($length+2-11) / $nr_lights;
  543. Log3 $name, 2, "$name: warning: offset for cmd $cmd is $offset instead of 50" if( $offset != 50 );
  544. my $autocreated = 0;
  545. for( my $i = 0; $i < $nr_lights; ++$i ) {
  546. my $short = substr($hex,$i*$offset*2+2*11,2*2);
  547. my $id = substr($hex,$i*$offset*2+2*13,2*8);
  548. my $type = substr($hex,$i*$offset*2+2*21,2*1);
  549. my $firmware = substr($hex,$i*$offset*2+2*22,2*4);
  550. my $reachable = hex(substr($hex,$i*$offset*2+2*26,2*1));
  551. my $groups = (substr($hex,$i*$offset*2+2*28,2*1).substr($hex,$i*$offset*2+2*27,2*1));
  552. my $onoff = hex(substr($hex,$i*$offset*2+2*29,2*1));
  553. my $dim = hex(substr($hex,$i*$offset*2+2*30,2*1));
  554. my $ct = hex(substr($hex,$i*$offset*2+2*32,2*1).substr($hex,$i*$offset*2+2*31,2*1));
  555. my $r = substr($hex,$i*$offset*2+2*33,2*1);
  556. my $g = substr($hex,$i*$offset*2+2*34,2*1);
  557. my $b = substr($hex,$i*$offset*2+2*35,2*1);
  558. my $w = substr($hex,$i*$offset*2+2*36,2*1);
  559. my $alias = pack('H*', substr($hex,$i*$offset*2+2*37,2*15));
  560. $alias =~ s/\x00//g;
  561. #my $count1 = substr($hex,$i*$offset*2+2*53,2*4); #reportMissingCount
  562. #my $count2 = substr($hex,$i*$offset*2+2*57,2*4); #pollingCount
  563. #Log 1, "count1: $count1, count2, $count2";
  564. my $has_ct = (hex($type) & 0x02) ? 1: 0;
  565. my $has_rgb = (hex($type) & 0x08) ? 1 : 0;
  566. my $is_sensor = (hex($type) >= 0x20) ? 1 : 0;
  567. #$has_ct = 1 if( $type eq '00' );
  568. 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";
  569. #my $code = $id;
  570. my $code = $name ."-". $id;
  571. $code = $name ."-S". $id if( $is_sensor );
  572. if( defined($modules{HUEDevice}{defptr}{$code}) ) {
  573. Log3 $name, 5, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'";
  574. } else {
  575. my $devname = "LIGHTIFY" . $id;
  576. #my $define= "$devname HUEDevice $id";
  577. my $define= "$devname HUEDevice $id IODev=$name";
  578. $define= "$devname HUEDevice sensor $id IODev=$name" if( $is_sensor );
  579. Log3 $name, 4, "$name: create new device '$devname' for address '$id'";
  580. my $cmdret= CommandDefine(undef,$define);
  581. if($cmdret) {
  582. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  583. } else {
  584. $cmdret = CommandAttr(undef,"$devname alias ".$alias);
  585. $cmdret = CommandAttr(undef,"$devname room LIGHTIFY");
  586. $cmdret = CommandAttr(undef,"$devname IODev $name");
  587. $autocreated++;
  588. }
  589. }
  590. if( my $chash = $modules{HUEDevice}{defptr}{$code} ) {
  591. $chash->{helper}{type} = hex($type);
  592. $chash->{helper}{type} = extcolordimmer if( !$chash->{helper}{type} );
  593. my $json = LIGHTIFY_toJson($hash, $chash, $id, $reachable, $onoff, $dim, $ct, $r, $g, $b);
  594. my $changed = HUEDevice_Parse( $chash, $json );
  595. if( $changed || $chash->{helper}{transitiontime} ) {
  596. RemoveInternalTimer($chash);
  597. InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0);
  598. $chash->{helper}{transitiontime} -= 1 if( $chash->{helper}{transitiontime} );
  599. }
  600. }
  601. }
  602. if( $autocreated ) {
  603. Log3 $name, 2, "$name: autocreated $autocreated devices";
  604. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  605. }
  606. RemoveInternalTimer($hash, "LIGHTIFY_poll");
  607. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "LIGHTIFY_poll", $hash, 0);
  608. } elsif( $cmd eq getGroups ) {
  609. my $nr_groups = hex(substr($hex,2*10,2*1).substr($hex,2*9,2*1));
  610. #Log 1, unpack 'v', pack 'H*', substr($hex,2*9,2*2);
  611. return undef if( !$nr_groups );
  612. my $offset = ($length+2-11) / $nr_groups;
  613. Log3 $name, 2, "$name: warning: offset for cmd $cmd is $offset instead of 18" if( $offset != 18 );
  614. my @groups;
  615. my $autocreated = 0;
  616. for( my $i = 0; $i <= $nr_groups; ++$i ) {
  617. my $id;
  618. my $alias;
  619. if( $i == 0 ) {
  620. $id = 0;
  621. $alias = 'Gruppe alles';
  622. } else {
  623. $id = hex(substr($hex,($i-1)*$offset*2+2*12,2*1).substr($hex,($i-1)*$offset*2+2*11,2*1));
  624. $alias = pack('H*', substr($hex,($i-1)*$offset*2+2*13,2*15));
  625. $alias =~ s/\x00//g;
  626. my $group = sprintf( "%02X", $id );
  627. $hash->{CL} = $hash->{helper}{CL};
  628. LIGHTIFY_sendRaw( $hash, '00', getGroupInfo ." 00 00 00 00 $group 00" );
  629. delete $hash->{CL};
  630. }
  631. push @groups, "$id: $alias";
  632. #my $code = $id;
  633. my $code = $name ."-G". $id;
  634. if( defined($modules{HUEDevice}{defptr}{$code}) ) {
  635. Log3 $name, 5, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'";
  636. } else {
  637. my $devname = "LIGHTIFYGroup" . $id;
  638. my $define= "$devname HUEDevice group $id IODev=$name";
  639. Log3 $name, 4, "$name: create new device '$devname' for group nr. '$id'";
  640. my $cmdret= CommandDefine(undef,$define);
  641. if($cmdret) {
  642. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  643. } else {
  644. $cmdret = CommandAttr(undef,"$devname alias ".$alias);
  645. $cmdret = CommandAttr(undef,"$devname room LIGHTIFY");
  646. $cmdret = CommandAttr(undef,"$devname IODev $name");
  647. $cmdret = CommandAttr(undef,"$devname subType switch") if( $id == 0 );
  648. $autocreated++;
  649. }
  650. }
  651. }
  652. Log3 $name, 4, "groups: " .join( ', ', @groups );
  653. if( $autocreated ) {
  654. Log3 $name, 2, "$name: autocreated $autocreated groups";
  655. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  656. }
  657. asyncOutput( $hash->{helper}{CL}, "got groups: ". join( ', ', @groups ) ) if( $hash->{helper}{CL} );
  658. } elsif( $cmd eq getGroupInfo ) {
  659. my $nr = hex(substr($hex,2*10,2*1).substr($hex,2*9,2*1));
  660. my $alias = pack('H*', substr($hex,2*11,2*15));
  661. my $nr_lights = hex(substr($hex,2*27,2*1));
  662. return undef if( !$nr_lights );
  663. $alias =~ s/\x00//g;
  664. my $offset = ($length+2-28) / $nr_lights; # should be 8
  665. Log3 $name, 2, "$name: warning: offset for cmd $cmd is $offset instead of 8" if( $offset != 8 );
  666. my @lights;
  667. for( my $i = 0; $i < $nr_lights; ++$i ) {
  668. my $light = substr($hex,$i*$offset*2+2*28,2*8);
  669. push @lights, $light;
  670. }
  671. Log3 $name, 4, "group $nr: alias: $alias, lights: " .join( ',', @lights );
  672. my $code = $name ."-G". $nr;
  673. if( my $chash = $modules{HUEDevice}{defptr}{$code} ) {
  674. $chash->{lights} = join( ',', @lights );
  675. }
  676. asyncOutput( $hash->{helper}{CL}, "group info: $nr: $alias, lights: ". join( ', ', @lights ) ) if( $hash->{helper}{CL} );
  677. } elsif( $cmd eq setOnOff ) {
  678. my $id = substr($hex,2*11,2*8);
  679. my $onoff = hex(substr($hex,2*19,2*1));
  680. } elsif( $cmd eq getStatus ) {
  681. my $id = substr($hex,2*11,2*8);
  682. my $json;
  683. my $code = $name ."-". $id;
  684. my $chash = $modules{HUEDevice}{defptr}{$code};
  685. if( !$chash ) {
  686. $code = $name ."-S". $id;
  687. $chash = $modules{HUEDevice}{defptr}{$code};
  688. }
  689. if( $length < 30 ) {
  690. $json = { state => { } };
  691. if( substr($hex,2*19,2*1) eq 'FF' ) {
  692. Log3 $name, 4, "$id, not reachable";
  693. $json = { state => { reachable => 0 } };
  694. }
  695. } else {
  696. my $reachable = hex(substr($hex,2*20,2*1));
  697. my $onoff = hex(substr($hex,2*21,2*1));
  698. my $dim = hex(substr($hex,2*22,2*1));
  699. my $ct = hex(substr($hex,2*24,2*1).substr($hex,2*23,2*1));
  700. my $r = substr($hex,2*25,2*1);
  701. my $g = substr($hex,2*26,2*1);
  702. my $b = substr($hex,2*27,2*1);
  703. my $w = substr($hex,2*28,2*1);
  704. Log3 $name, 4, "$id, reachable: $reachable, onoff: $onoff, dim: $dim, ct: $ct, rgb: $r$g$b, w: $w";
  705. $json = LIGHTIFY_toJson($hash, $chash, $id, $reachable, $onoff, $dim, $ct, $r, $g, $b);
  706. }
  707. my $changed = HUEDevice_Parse( $chash, $json ) if( $chash );
  708. if( $changed || $chash->{helper}{transitiontime} ) {
  709. RemoveInternalTimer($chash);
  710. InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0);
  711. $chash->{helper}{transitiontime} -= 1 if( $chash->{helper}{transitiontime} );
  712. }
  713. } else {
  714. Log3 $name, 4, "$name: unhandled message $hex ";
  715. }
  716. }
  717. sub
  718. LIGHTIFY_Read($)
  719. {
  720. my ($hash) = @_;
  721. my $name = $hash->{NAME};
  722. my $buf;
  723. my $ret = sysread($hash->{CD}, $buf, 1024);
  724. if( !defined($ret) || !$ret ) {
  725. Log3 $name, 4, "$name: disconnected";
  726. LIGHTIFY_Disconnect($hash);
  727. InternalTimer(gettimeofday()+10, "LIGHTIFY_Connect", $hash, 0);
  728. return;
  729. }
  730. my $hex = unpack('H*', $buf);
  731. Log3 $name, 5, "$name: received: $hex";
  732. $hash->{PARTIAL} .= $hex;
  733. my $length = hex(substr($hash->{PARTIAL},2*1,2*1).substr($hash->{PARTIAL},2*0,2*1));
  734. while( $hash->{PARTIAL} && $length+2 <= length($hash->{PARTIAL})/2 ) {
  735. $hex = substr($hash->{PARTIAL},0,$length*2+2*2);
  736. $hash->{PARTIAL} = substr($hash->{PARTIAL},$length*2+2*2);
  737. $length = hex(substr($hash->{PARTIAL},2*1,2*1).substr($hash->{PARTIAL},2*0,2*1)) if( $hash->{PARTIAL} );
  738. LIGHTIFY_Parse($hash, $hex);
  739. }
  740. readingsSingleUpdate($hash, 'state', $hash->{READINGS}{state}{VAL}, 0);
  741. LIGHTIFY_sendNext( $hash ) if( !$hash->{PARTIAL} );
  742. #RemoveInternalTimer($hash);
  743. #InternalTimer(gettimeofday()+2, "LIGHTIFY_sendNext", $hash, 0);
  744. }
  745. 1;
  746. =pod
  747. =item summary module for the osram lightify gateway
  748. =item summary_DE Modul f&uuml;r das Osram LIGHTFY Gateway
  749. =begin html
  750. <a name="LIGHTIFY"></a>
  751. <h3>LIGHTIFY</h3>
  752. <ul>
  753. Module to integrate a OSRAM LIGHTIFY gateway into FHEM;.<br><br>
  754. The actual LIGHTIFY lights are defined as <a href="#HUEDevice">HUEDevice</a> devices.
  755. <br><br>
  756. All newly found devices and groups are autocreated at startup and added to the room LIGHTIFY.
  757. <br><br>
  758. Notes:
  759. <ul>
  760. <li>Autocreate only works for the first gateway. Devices on other gateways have to be manualy defined.</li>
  761. </ul>
  762. <br><br>
  763. <a name="LIGTHIFY_Define"></a>
  764. <b>Define</b>
  765. <ul>
  766. <code>define &lt;name&gt; LIGHTIFY &lt;host&gt;</code><br>
  767. <br>
  768. Defines a LIGHTIFY gateway device with address &lt;host&gt;.<br><br>
  769. Examples:
  770. <ul>
  771. <code>define gateway LIGHTIFY 10.0.1.100</code><br>
  772. </ul>
  773. </ul><br>
  774. <a name="LIGHTIFY_Get"></a>
  775. <b>Get</b>
  776. <ul>
  777. </ul><br>
  778. <a name="LIGHTIFY_Set"></a>
  779. <b>Set</b>
  780. <ul>
  781. <li>on</li>
  782. <li>off</li>
  783. <li>goToScene &lt;sceneId&gt;</li>
  784. <li>setSoftOn &lt;addr&gt; &lt;transitiontime&gt;</li>
  785. <li>setSoftOff &lt;addr&gt; &lt;transitiontime&gt;</li>
  786. <li>reconnect<br>
  787. Closes and reopens the connection to the gateway.</li>
  788. <li>statusRequest<br>
  789. Update light status.</li>
  790. </ul><br>
  791. <a name="LIGHTIFY_Attr"></a>
  792. <b>Attributes</b>
  793. <ul>
  794. <li><a href="#disable">disable</a></li>
  795. <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
  796. </ul><br>
  797. </ul><br>
  798. =end html
  799. =cut