30_LIGHTIFY.pm 31 KB

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