31_Aurora.pm 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028
  1. # $Id: 31_Aurora.pm 17036 2018-07-27 13:07:51Z justme1968 $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use Color;
  6. use POSIX;
  7. use JSON;
  8. use SetExtensions;
  9. use vars qw(%FW_webArgs); # all arguments specified in the GET
  10. my %dim_values = (
  11. 0 => "dim06%",
  12. 1 => "dim12%",
  13. 2 => "dim18%",
  14. 3 => "dim25%",
  15. 4 => "dim31%",
  16. 5 => "dim37%",
  17. 6 => "dim43%",
  18. 7 => "dim50%",
  19. 8 => "dim56%",
  20. 9 => "dim62%",
  21. 10 => "dim68%",
  22. 11 => "dim75%",
  23. 12 => "dim81%",
  24. 13 => "dim87%",
  25. 14 => "dim93%",
  26. );
  27. my $Aurora_hasDataDumper = 1;
  28. sub
  29. Aurora_Initialize($)
  30. {
  31. my ($hash) = @_;
  32. # Provide
  33. #Consumer
  34. $hash->{DefFn} = "Aurora_Define";
  35. $hash->{NotifyFn} = "Aurora_Notify";
  36. $hash->{UndefFn} = "Aurora_Undefine";
  37. $hash->{SetFn} = "Aurora_Set";
  38. $hash->{GetFn} = "Aurora_Get";
  39. $hash->{AttrFn} = "Aurora_Attr";
  40. $hash->{AttrList} = "delayedUpdate:1 ".
  41. "realtimePicker:1,0 ".
  42. "color-icons:1,2 ".
  43. "transitiontime ".
  44. "token ".
  45. "disable:1,0 disabledForIntervals ".
  46. $readingFnAttributes;
  47. #$hash->{FW_summaryFn} = "Aurora_summaryFn";
  48. FHEM_colorpickerInit();
  49. eval "use Data::Dumper";
  50. $Aurora_hasDataDumper = 0 if($@);
  51. }
  52. sub
  53. Aurora_devStateIcon($)
  54. {
  55. my($hash) = @_;
  56. $hash = $defs{$hash} if( ref($hash) ne 'HASH' );
  57. return undef if( !$hash );
  58. my $name = $hash->{NAME};
  59. return ".*:off:toggle" if( ReadingsVal($name,"state","off") eq "off" );
  60. return ".*:on:toggle" if( ReadingsVal($name,"effect","*Solid*") ne "*Solid*" );
  61. my $pct = ReadingsVal($name,"pct","100");
  62. my $s = $dim_values{int($pct/7)};
  63. $s="on" if( $pct eq "100" );
  64. #return ".*:$s:toggle" if( AttrVal($name, "model", "") eq "LWB001" );
  65. #return ".*:$s:toggle" if( AttrVal($name, "model", "") eq "LWB003" );
  66. #return ".*:$s:toggle" if( AttrVal($name, "model", "") eq "LWB004" );
  67. return ".*:$s@#".CommandGet("","$name RGB").":toggle" if( $pct < 100 && AttrVal($name, "color-icons", 0) == 2 );
  68. return ".*:on@#".CommandGet("","$name rgb").":toggle" if( AttrVal($name, "color-icons", 0) != 0 );
  69. return '<div style="width:32px;height:19px;'.
  70. 'border:1px solid #fff;border-radius:8px;background-color:#'.CommandGet("","$name rgb").';"></div>';
  71. }
  72. sub
  73. Aurora_summaryFn($$$$)
  74. {
  75. my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
  76. my $hash = $defs{$d};
  77. my $name = $hash->{NAME};
  78. return Aurora_devStateIcon($hash);
  79. }
  80. sub
  81. Aurora_Define($$)
  82. {
  83. my ($hash, $def) = @_;
  84. my @args = split("[ \t]+", $def);
  85. return "Usage: define <name> Aurora <ip> [interval]" if(@args < 3);
  86. my ($name, $type, $ip, $interval) = @args;
  87. $hash->{STATE} = 'Initialized';
  88. #pair & get mac
  89. $hash->{IP} = $ip;
  90. my $code = $hash->{IP};
  91. my $d = $modules{Aurora}{defptr}{$code};
  92. return "Aurora device $hash->{ID} already defined as $d->{NAME}."
  93. if( defined($d) && $d->{NAME} ne $name );
  94. $modules{Aurora}{defptr}{$code} = $hash;
  95. $args[3] = "" if( !defined( $args[3] ) );
  96. $interval = 60 if( defined($interval) && $interval < 10 );
  97. $hash->{INTERVAL} = $interval;
  98. $hash->{helper}{last_config_timestamp} = 0;
  99. $hash->{helper}{on} = -1;
  100. $hash->{helper}{colormode} = '';
  101. $hash->{helper}{ct} = -1;
  102. $hash->{helper}{hue} = -1;
  103. $hash->{helper}{sat} = -1;
  104. $hash->{helper}{xy} = '';
  105. $hash->{helper}{effect} = '';
  106. $hash->{helper}{pct} = -1;
  107. $hash->{helper}{rgb} = "";
  108. $attr{$name}{devStateIcon} = '{(Aurora_devStateIcon($name),"toggle")}' if( !defined( $attr{$name}{devStateIcon} ) );
  109. my $icon_path = AttrVal("WEB", "iconPath", "default:fhemSVG:openautomation" );
  110. $attr{$name}{'color-icons'} = 2 if( !defined( $attr{$name}{'color-icons'} ) && $icon_path =~ m/openautomation/ );
  111. $hash->{NOTIFYDEV} = "global";
  112. RemoveInternalTimer($hash);
  113. if( $init_done ) {
  114. Aurora_OpenDev($hash) if( !IsDisabled($name) );
  115. }
  116. return undef;
  117. }
  118. sub
  119. Aurora_Notify($$)
  120. {
  121. my ($hash,$dev) = @_;
  122. my $name = $hash->{NAME};
  123. my $type = $hash->{TYPE};
  124. return if($dev->{NAME} ne "global");
  125. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  126. if( IsDisabled($name) > 0 ) {
  127. readingsSingleUpdate($hash, 'state', 'inactive', 1 ) if( ReadingsVal($name,'inactive','' ) ne 'disabled' );
  128. return undef;
  129. }
  130. Aurora_OpenDev($hash);
  131. return undef;
  132. }
  133. sub
  134. Aurora_Undefine($$)
  135. {
  136. my ($hash,$arg) = @_;
  137. RemoveInternalTimer($hash);
  138. my $code = $hash->{IP};
  139. delete($modules{Aurora}{defptr}{$code});
  140. return undef;
  141. }
  142. sub
  143. Aurora_OpenDev($)
  144. {
  145. my ($hash) = @_;
  146. my $name = $hash->{NAME};
  147. if( !AttrVal($name, 'token', undef) ) {
  148. Aurora_Pair($hash);
  149. } else {
  150. RemoveInternalTimer($hash);
  151. Aurora_GetUpdate($hash);
  152. }
  153. return undef;
  154. Aurora_Detect($hash) if( defined($hash->{NUPNP}) );
  155. my $result = Aurora_Call($hash, undef, 'config', undef);
  156. if( !defined($result) ) {
  157. Log3 $name, 2, "Aurora_OpenDev: got empty config";
  158. return undef;
  159. }
  160. Log3 $name, 5, "Aurora_OpenDev: got config " . Dumper $result if($Aurora_hasDataDumper);
  161. if( !defined($result->{'linkbutton'}) || !AttrVal($name, 'key', undef) )
  162. {
  163. Aurora_fillBridgeInfo($hash, $result);
  164. Aurora_Pair($hash);
  165. return;
  166. }
  167. $hash->{mac} = $result->{'mac'};
  168. readingsSingleUpdate($hash, 'state', 'connected', 1 );
  169. Aurora_GetUpdate($hash);
  170. Aurora_Autocreate($hash);
  171. return undef;
  172. }
  173. sub
  174. Aurora_Pair($)
  175. {
  176. my ($hash) = @_;
  177. my $name = $hash->{NAME};
  178. readingsSingleUpdate($hash, 'state', 'pairing', 1 );
  179. my($err,$data) = HttpUtils_NonblockingGet({
  180. url => "http://$hash->{IP}:16021/api/v1/new",
  181. timeout => 2,
  182. method => 'POST',
  183. noshutdown => $hash->{noshutdown},
  184. hash => $hash,
  185. type => 'pair',
  186. callback => \&Aurora_dispatch,
  187. });
  188. return undef;
  189. my $result = Aurora_Register($hash);
  190. if( $result->{'error'} )
  191. {
  192. RemoveInternalTimer($hash);
  193. InternalTimer(gettimeofday()+5, "Aurora_Pair", $hash, 0);
  194. return undef;
  195. }
  196. $attr{$name}{token} = $result->{success}{username} if( $result->{success}{username} );
  197. readingsSingleUpdate($hash, 'state', 'paired', 1 );
  198. Aurora_OpenDev($hash);
  199. return undef;
  200. }
  201. sub
  202. Aurora_dispatch($$$;$)
  203. {
  204. my ($param, $err, $data) = @_;
  205. my $hash = $param->{hash};
  206. my $name = $hash->{NAME};
  207. my $json;
  208. $json = eval { decode_json($data) } if( $data );
  209. Log3 $name, 2, "$name: json error: $@ in $data" if( $@ );
  210. #Log 1, " $err";
  211. #Log 1, " $data";
  212. #Log 1, " $json";
  213. if( $param->{type} eq 'pair' ) {
  214. if( !$json ) {
  215. RemoveInternalTimer($hash);
  216. InternalTimer(gettimeofday()+5, "Aurora_Pair", $hash, 0);
  217. return undef;
  218. } else {
  219. $attr{$name}{token} = $json->{auth_token} if( $json->{auth_token} );
  220. Aurora_GetUpdate($hash);
  221. }
  222. }
  223. #return undef if( !$json );
  224. if( $param->{type} eq 'state' ) {
  225. if( $param->{method} eq 'GET' ) {
  226. Aurora_Parse($hash, $json) if( $json );
  227. } else {
  228. RemoveInternalTimer($hash);
  229. InternalTimer(gettimeofday()+1, "Aurora_GetUpdate", $hash, 0);
  230. }
  231. }
  232. }
  233. sub
  234. Aurora_SetParam($$@)
  235. {
  236. my ($name, $obj, $cmd, $value, $value2, @a) = @_;
  237. if( $cmd eq "color" ) {
  238. $value = int(1000000/$value);
  239. $cmd = 'ct';
  240. } elsif( $name && $cmd eq "toggle" ) {
  241. $cmd = ReadingsVal($name,"onoff",1) ? "off" :"on";
  242. } elsif( $cmd =~ m/^dim(\d+)/ ) {
  243. $value2 = $value;
  244. $value = $1;
  245. $value = 0 if( $value < 0 );
  246. $value = 100 if( $value > 100 );
  247. $cmd = 'pct';
  248. } elsif( !defined($value) && $cmd =~ m/^(\d+)/) {
  249. $value2 = $value;
  250. $value = $1;
  251. $value = 0 if( $value < 0 );
  252. $value = 100 if( $value > 100 );
  253. $cmd = 'pct';
  254. }
  255. $cmd = "off" if($cmd eq "pct" && $value == 0 );
  256. if($cmd eq 'on') {
  257. $obj->{'on'} = JSON::true;
  258. $obj->{'transitiontime'} = $value * 10 if( defined($value) );
  259. } elsif($cmd eq 'off') {
  260. $obj->{'on'} = JSON::false;
  261. $obj->{'transitiontime'} = $value * 10 if( defined($value) );
  262. } elsif($cmd eq "pct") {
  263. $value = 0 if( $value < 0 );
  264. $value = 100 if( $value > 100 );
  265. $obj->{'on'} = JSON::true;
  266. $obj->{'brightness'} = int($value);
  267. $obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
  268. } elsif($name && $cmd eq "dimUp") {
  269. my $pct = ReadingsVal($name,"pct","0");
  270. $pct += 10;
  271. $pct = 100 if( $pct > 100 );
  272. $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
  273. $obj->{'brightness'} = 0+$pct;
  274. $obj->{'transitiontime'} = 1;
  275. #$obj->{'transitiontime'} = $value * 10 if( defined($value) );
  276. $defs{$name}->{helper}->{update_timeout} = 0;
  277. } elsif($name && $cmd eq "dimDown") {
  278. my $pct = ReadingsVal($name,"pct","0");
  279. $pct -= 10;
  280. $pct = 0 if( $pct < 0 );
  281. $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
  282. $obj->{'brightness'} = 0+$pct;
  283. $obj->{'transitiontime'} = 1;
  284. #$obj->{'transitiontime'} = $value * 10 if( defined($value) );
  285. $defs{$name}->{helper}->{update_timeout} = 0;
  286. } elsif($cmd eq "satUp") {
  287. $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
  288. $obj->{'sat_inc'} = 10;
  289. $obj->{'sat_inc'} = 0+$value if( defined($value) );
  290. } elsif($cmd eq "satDown") {
  291. $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
  292. $obj->{'sat_inc'} = -10;
  293. $obj->{'sat_inc'} = 0+$value if( defined($value) );
  294. } elsif($cmd eq "hueUp") {
  295. $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
  296. $obj->{'hue_inc'} = 30;
  297. $obj->{'hue_inc'} = 0+$value if( defined($value) );
  298. } elsif($cmd eq "hueDown") {
  299. $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
  300. $obj->{'hue_inc'} = -30;
  301. $obj->{'hue_inc'} = 0+$value if( defined($value) );
  302. } elsif($cmd eq "ctUp") {
  303. $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
  304. $obj->{'ct_inc'} = 16;
  305. $obj->{'ct_inc'} = 0+$value if( defined($value) );
  306. } elsif($cmd eq "ctDown") {
  307. $obj->{'on'} = JSON::true if( !$defs{$name}->{helper}{on} );
  308. $obj->{'ct_inc'} = -16;
  309. $obj->{'ct_inc'} = 0+$value if( defined($value) );
  310. } elsif($cmd eq "ct") {
  311. $obj->{'on'} = JSON::true;
  312. $value = int(1000000/$value) if( $value < 1000 );
  313. $obj->{'ct'} = 0+$value;
  314. $obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
  315. } elsif($cmd eq "hue") {
  316. $obj->{'on'} = JSON::true;
  317. $obj->{'hue'} = 0+$value;
  318. $obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
  319. } elsif($cmd eq "sat") {
  320. $obj->{'on'} = JSON::true;
  321. $obj->{'sat'} = 0+$value;
  322. $obj->{'transitiontime'} = $value2 * 10 if( defined($value2) );
  323. } elsif( $cmd eq "rgb" && $value =~ m/^(..)(..)(..)/) {
  324. my( $r, $g, $b ) = (hex($1)/255.0, hex($2)/255.0, hex($3)/255.0);
  325. my( $h, $s, $v ) = Color::rgb2hsv($r,$g,$b);
  326. $obj->{'on'} = JSON::true;
  327. $obj->{'hue'} = int( $h * 359 );
  328. $obj->{'sat'} = int( $s * 100 );
  329. $obj->{'brightness'} = int( $v * 100 );
  330. } elsif( $cmd eq "hsv" && $value =~ m/^(..)(..)(..)/) {
  331. my( $h, $s, $v ) = (hex($1), hex($2), hex($3));
  332. $s = 100 if( $s > 100 );
  333. $v = 100 if( $v > 100 );
  334. $obj->{'on'} = JSON::true;
  335. $obj->{'hue'} = int($h*100);
  336. $obj->{'sat'} = 0+$s;
  337. $obj->{'brightness'} = 0+$v;
  338. } elsif( $cmd eq "effect" ) {
  339. $obj->{'select'} = "$value";
  340. $obj->{'select'} .= " $value2" if( $value2 );
  341. $obj->{'select'} .= " ". join(" ", @a) if( @a );
  342. } elsif( $cmd eq "transitiontime" ) {
  343. $obj->{'transitiontime'} = 0+$value;
  344. } elsif( $name && $cmd eq "delayedUpdate" ) {
  345. $defs{$name}->{helper}->{update_timeout} = 1;
  346. } elsif( $name && $cmd eq "immediateUpdate" ) {
  347. $defs{$name}->{helper}->{update_timeout} = 0;
  348. } elsif( $name && $cmd eq "noUpdate" ) {
  349. $defs{$name}->{helper}->{update_timeout} = -1;
  350. } else {
  351. return 0;
  352. }
  353. return 1;
  354. }
  355. sub
  356. Aurora_Set($@)
  357. {
  358. my ($hash, $name, @aa) = @_;
  359. my ($cmd, @args) = @aa;
  360. my %obj;
  361. $hash->{helper}->{update_timeout} = AttrVal($name, "delayedUpdate", 1);
  362. if( (my $joined = join(" ", @aa)) =~ /:/ ) {
  363. my @cmds = split(":", $joined);
  364. for( my $i = 0; $i <= $#cmds; ++$i ) {
  365. Aurora_SetParam($name, \%obj, split(" ", $cmds[$i]) );
  366. }
  367. } else {
  368. my ($cmd, $value, $value2, @a) = @aa;
  369. if( $cmd eq "statusRequest" ) {
  370. RemoveInternalTimer($hash);
  371. Aurora_GetUpdate($hash);
  372. return undef;
  373. }
  374. Aurora_SetParam($name, \%obj, $cmd, $value, $value2, @a);
  375. }
  376. #Log 1, Dumper \%obj;
  377. if( %obj ) {
  378. if( defined($obj{on}) ) {
  379. $hash->{desired} = $obj{on}?1:0;
  380. }
  381. if( !defined($obj{transitiontime}) ) {
  382. my $transitiontime = AttrVal($name, "transitiontime", undef);
  383. $obj{transitiontime} = 0 + $transitiontime if( defined( $transitiontime ) );
  384. }
  385. }
  386. if( scalar keys %obj ) {
  387. my($err,$data) = HttpUtils_NonblockingGet({
  388. url => "http://$hash->{IP}:16021/api/v1/$attr{$name}{token}/".($obj{select}?"effects":"state"),
  389. timeout => 2,
  390. method => 'PUT',
  391. noshutdown => $hash->{noshutdown},
  392. hash => $hash,
  393. type => 'state',
  394. data => encode_json(\%obj),
  395. callback => \&Aurora_dispatch,
  396. });
  397. SetExtensionsCancel($hash);
  398. $hash->{".triggerUsed"} = 1;
  399. return undef;
  400. }
  401. my $list = "off:noArg on:noArg toggle:noArg statusRequest:noArg";
  402. $list .= " pct:colorpicker,BRI,0,1,100";
  403. $list .= " rgb:colorpicker,RGB";
  404. $list .= " color:colorpicker,CT,1200,10,6500";
  405. $list .= " hue:colorpicker,HUE,0,1,359 sat:slider,0,1,100";
  406. $list .= " dimUp:noArg dimDown:noArg";
  407. #$list .= " alert:none,select,lselect";
  408. #$list .= " dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%";
  409. if( $hash->{helper}{effects} ) {
  410. my $effects = join(',',@{$hash->{helper}{effects}});
  411. $effects =~ s/\s/#/g;
  412. $list .= " effect:,$effects";
  413. }
  414. return SetExtensions($hash, $list, $name, @aa);
  415. }
  416. sub
  417. cttorgb($)
  418. {
  419. my ($ct) = @_;
  420. # calculation from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code
  421. # adjusted by 1000K
  422. my $temp = (1000000/$ct)/100 + 10;
  423. my $r = 0;
  424. my $g = 0;
  425. my $b = 0;
  426. $r = 255;
  427. $r = 329.698727446 * ($temp - 60) ** -0.1332047592 if( $temp > 66 );
  428. $r = 0 if( $r < 0 );
  429. $r = 255 if( $r > 255 );
  430. if( $temp <= 66 ) {
  431. $g = 99.4708025861 * log($temp) - 161.1195681661;
  432. } else {
  433. $g = 288.1221695283 * ($temp - 60) ** -0.0755148492;
  434. }
  435. $g = 0 if( $g < 0 );
  436. $g = 255 if( $g > 255 );
  437. $b = 255;
  438. $b = 0 if( $temp <= 19 );
  439. if( $temp < 66 ) {
  440. $b = 138.5177312231 * log($temp-10) - 305.0447927307;
  441. }
  442. $b = 0 if( $b < 0 );
  443. $b = 255 if( $b > 255 );
  444. return( $r, $g, $b );
  445. }
  446. sub
  447. xyYtorgb($$$)
  448. {
  449. # calculation from http://www.brucelindbloom.com/index.html
  450. my ($x,$y,$Y) = @_;
  451. #Log 3, "xyY:". $x . " " . $y ." ". $Y;
  452. my $r = 0;
  453. my $g = 0;
  454. my $b = 0;
  455. if( $y > 0 ) {
  456. my $X = $x * $Y / $y;
  457. my $Z = (1 - $x - $y)*$Y / $y;
  458. if( $X > 1
  459. || $Y > 1
  460. || $Z > 1 ) {
  461. my $f = maxNum($X,$Y,$Z);
  462. $X /= $f;
  463. $Y /= $f;
  464. $Z /= $f;
  465. }
  466. #Log 3, "XYZ: ". $X . " " . $Y ." ". $Y;
  467. $r = 0.7982 * $X + 0.3389 * $Y - 0.1371 * $Z;
  468. $g = -0.5918 * $X + 1.5512 * $Y + 0.0406 * $Z;
  469. $b = 0.0008 * $X + 0.0239 * $Y + 0.9753 * $Z;
  470. if( $r > 1
  471. || $g > 1
  472. || $b > 1 ) {
  473. my $f = maxNum($r,$g,$b);
  474. $r /= $f;
  475. $g /= $f;
  476. $b /= $f;
  477. }
  478. #Log 3, "rgb: ". $r . " " . $g ." ". $b;
  479. $r *= 255;
  480. $g *= 255;
  481. $b *= 255;
  482. }
  483. return( $r, $g, $b );
  484. }
  485. sub
  486. Aurora_Get($@)
  487. {
  488. my ($hash, @a) = @_;
  489. my $name = $a[0];
  490. return "$name: get needs at least one parameter" if(@a < 2);
  491. my $cmd= $a[1];
  492. if($cmd eq "rgb") {
  493. my $r = 0;
  494. my $g = 0;
  495. my $b = 0;
  496. my $cm = ReadingsVal($name,"colormode","");
  497. if( $cm eq "ct" ) {
  498. if( ReadingsVal($name,"ct","") =~ m/(\d+)/ ) {
  499. ($r,$g,$b) = cttorgb(1000000/$1);
  500. }
  501. } else {
  502. my $h = ReadingsVal($name,"hue",0) / 359.0;
  503. my $s = ReadingsVal($name,"sat",0) / 100.0;
  504. my $v = ReadingsVal($name,"pct",0) / 100.0;
  505. ($r,$g,$b) = Color::hsv2rgb($h,$s,$v);
  506. $r *= 255;
  507. $g *= 255;
  508. $b *= 255;
  509. }
  510. return sprintf( "%02x%02x%02x", $r+0.5, $g+0.5, $b+0.5 );
  511. } elsif($cmd eq "RGB") {
  512. my $r = 0;
  513. my $g = 0;
  514. my $b = 0;
  515. my $cm = ReadingsVal($name,"colormode","");
  516. if( $cm eq "ct" ) {
  517. if( ReadingsVal($name,"ct","") =~ m/(\d+) .*/ ) {
  518. ($r,$g,$b) = cttorgb($1);
  519. }
  520. } else {
  521. my $h = ReadingsVal($name,"hue",0) / 359.0;
  522. my $s = ReadingsVal($name,"sat",0) / 100.0;
  523. my $v = 1;
  524. ($r,$g,$b) = Color::hsv2rgb($h,$s,$v);
  525. $r *= 255;
  526. $g *= 255;
  527. $b *= 255;
  528. }
  529. return sprintf( "%02x%02x%02x", $r+0.5, $g+0.5, $b+0.5 );
  530. } elsif ( $cmd eq "devStateIcon" ) {
  531. return Aurora_devStateIcon($hash);
  532. }
  533. return "Unknown argument $cmd, choose one of rgb:noArg RGB:noArg devStateIcon:noArg";
  534. }
  535. ###################################
  536. # This could be IORead in fhem, But there is none.
  537. # Read http://forum.fhem.de/index.php?t=tree&goto=54027&rid=10#msg_54027
  538. # to find out why.
  539. sub
  540. Aurora_ReadFromServer($@)
  541. {
  542. my ($hash,@a) = @_;
  543. my $name = $hash->{NAME};
  544. no strict "refs";
  545. my $ret;
  546. unshift(@a,$name);
  547. #$ret = IOWrite($hash, @a);
  548. $ret = IOWrite($hash,$hash,@a);
  549. use strict "refs";
  550. return $ret;
  551. return if(IsDummy($name) || IsIgnored($name));
  552. my $iohash = $hash->{IODev};
  553. if(!$iohash ||
  554. !$iohash->{TYPE} ||
  555. !$modules{$iohash->{TYPE}} ||
  556. !$modules{$iohash->{TYPE}}{ReadFn}) {
  557. Log3 $name, 5, "No I/O device or ReadFn found for $name";
  558. return;
  559. }
  560. no strict "refs";
  561. #my $ret;
  562. unshift(@a,$name);
  563. $ret = &{$modules{$iohash->{TYPE}}{ReadFn}}($iohash, @a);
  564. use strict "refs";
  565. return $ret;
  566. }
  567. sub
  568. Aurora_GetUpdate($)
  569. {
  570. my ($hash) = @_;
  571. my $name = $hash->{NAME};
  572. if(!$hash->{LOCAL}) {
  573. RemoveInternalTimer($hash);
  574. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "Aurora_GetUpdate", $hash, 0) if( $hash->{INTERVAL} );
  575. }
  576. return undef if(IsDisabled($name));
  577. my ($now) = gettimeofday();
  578. if( $hash->{LOCAL} || $now - $hash->{helper}{last_config_timestamp} > 300 ) {
  579. my($err,$data) = HttpUtils_NonblockingGet({
  580. url => "http://$hash->{IP}:16021/api/v1/$attr{$name}{token}",
  581. timeout => 2,
  582. method => 'GET',
  583. noshutdown => $hash->{noshutdown},
  584. hash => $hash,
  585. type => 'state',
  586. callback => \&Aurora_dispatch,
  587. });
  588. $hash->{helper}{last_config_timestamp} = $now;
  589. } else {
  590. my($err,$data) = HttpUtils_NonblockingGet({
  591. url => "http://$hash->{IP}:16021/api/v1/$attr{$name}{token}/state",
  592. timeout => 2,
  593. method => 'GET',
  594. noshutdown => $hash->{noshutdown},
  595. hash => $hash,
  596. type => 'state',
  597. callback => \&Aurora_dispatch,
  598. });
  599. }
  600. return undef;
  601. }
  602. sub
  603. AuroraSetIcon($;$)
  604. {
  605. my ($hash,$force) = @_;
  606. $hash = $defs{$hash} if( ref($hash) ne 'HASH' );
  607. return undef if( !$hash );
  608. my $name = $hash->{NAME};
  609. return if( defined($attr{$name}{icon}) && !$force );
  610. }
  611. sub
  612. Aurora_Parse($$)
  613. {
  614. my($hash,$result) = @_;
  615. my $name = $hash->{NAME};
  616. if( ref($result) ne "HASH" ) {
  617. if( ref($result) && $Aurora_hasDataDumper) {
  618. Log3 $name, 2, "$name: got wrong status message for $name: ". Dumper $result;
  619. } else {
  620. Log3 $name, 2, "$name: got wrong status message for $name: $result";
  621. }
  622. return undef;
  623. }
  624. Log3 $name, 4, "parse status message for $name";
  625. Log3 $name, 5, Dumper $result if($Aurora_hasDataDumper);
  626. $hash->{name} = $result->{name} if( defined($result->{name}) );
  627. $hash->{serialNo} = $result->{serialNo} if( defined($result->{serialNo}) );
  628. $hash->{manufacturer} = $result->{manufacturer} if( defined($result->{manufacturer}) );
  629. $hash->{model} = $result->{model} if( defined($result->{model}) );
  630. $hash->{firmwareVersion} = $result->{firmwareVersion} if( defined($result->{firmwareVersion}) );
  631. if( my $effects = $result->{effects} ) {
  632. $hash->{helper}{effects} = $effects->{effectsList} if( defined($effects->{effectsList}) );
  633. if( my $effect = $effects->{select} ) {
  634. if( $effect ne $hash->{helper}{effect} ) { readingsSingleUpdate($hash, 'effect', $effect, 1 ) };
  635. $hash->{helper}{effect} = $effect;
  636. }
  637. }
  638. $attr{$name}{devStateIcon} = '{(Aurora_devStateIcon($name),"toggle")}' if( !defined( $attr{$name}{devStateIcon} ) );
  639. if( !defined($attr{$name}{webCmd}) ) {
  640. $attr{$name}{webCmd} = 'rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:ct 490:ct 380:ct 270:ct 160:effect:on:off';
  641. #$attr{$name}{webCmd} = 'hue:rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:toggle:on:off';
  642. #$attr{$name}{webCmd} = 'ct:ct 490:ct 380:ct 270:ct 160:toggle:on:off';
  643. #$attr{$name}{webCmd} = 'pct:toggle:on:off';
  644. #$attr{$name}{webCmd} = 'toggle:on:off';
  645. }
  646. readingsBeginUpdate($hash);
  647. my $state = $result;
  648. $state = $state->{'state'} if( defined($state->{'state'}) );
  649. my $on = $state->{on}{value};
  650. $on = $hash->{helper}{on} if( !defined($on) );
  651. my $colormode = $state->{'colorMode'};
  652. my $pct = $state->{'brightness'}{value};
  653. $pct = $hash->{helper}{pct} if( !defined($pct) );
  654. my $ct = $state->{'ct'}{value};
  655. my $hue = $state->{'hue'}{value};
  656. my $sat = $state->{'sat'}{value};
  657. my $alert = $state->{alert};
  658. my $effect = $state->{effect};
  659. if( defined($colormode) && $colormode ne $hash->{helper}{colormode} ) {readingsBulkUpdate($hash,"colormode",$colormode);}
  660. if( defined($ct) && $ct != $hash->{helper}{ct} ) {
  661. if( $ct == 0 ) {
  662. readingsBulkUpdate($hash,"ct",$ct);
  663. }
  664. else {
  665. readingsBulkUpdate($hash,"ct",$ct);
  666. }
  667. }
  668. if( defined($hue) && $hue != $hash->{helper}{hue} ) {readingsBulkUpdate($hash,"hue",$hue);}
  669. if( defined($sat) && $sat != $hash->{helper}{sat} ) {readingsBulkUpdate($hash,"sat",$sat);}
  670. if( defined($alert) && $alert ne $hash->{helper}{alert} ) {readingsBulkUpdate($hash,"alert",$alert);}
  671. if( defined($effect) && $effect ne $hash->{helper}{effect} ) {readingsBulkUpdate($hash,"effect",$effect);}
  672. my $s = '';
  673. if( $on )
  674. {
  675. $s = 'on';
  676. if( $on != $hash->{helper}{on} ) {readingsBulkUpdate($hash,"onoff",1);}
  677. $s = 'off' if( $pct == 0 );
  678. }
  679. else
  680. {
  681. $on = 0;
  682. $s = 'off';
  683. $pct = 0;
  684. if( $on != $hash->{helper}{on} ) {readingsBulkUpdate($hash,"onoff",0);}
  685. }
  686. if( $pct != $hash->{helper}{pct} ) {readingsBulkUpdate($hash,"pct", $pct);}
  687. #if( $pct != $hash->{helper}{pct} ) {readingsBulkUpdate($hash,"level", $pct . ' %');}
  688. $hash->{helper}{on} = $on if( defined($on) );
  689. $hash->{helper}{colormode} = $colormode if( defined($colormode) );
  690. $hash->{helper}{ct} = $ct if( defined($ct) );
  691. $hash->{helper}{hue} = $hue if( defined($hue) );
  692. $hash->{helper}{sat} = $sat if( defined($sat) );
  693. $hash->{helper}{alert} = $alert if( defined($alert) );
  694. $hash->{helper}{effect} = $effect if( defined($effect) );
  695. $hash->{helper}{pct} = $pct;
  696. my $changed = $hash->{CHANGED}?1:0;
  697. if( $s ne $hash->{STATE} ) {readingsBulkUpdate($hash,"state",$s);}
  698. readingsEndUpdate($hash,1);
  699. if( defined($colormode) ) {
  700. my $rgb = CommandGet("","$name rgb");
  701. if( $rgb ne $hash->{helper}{rgb} ) { readingsSingleUpdate($hash,"rgb", $rgb,1); };
  702. $hash->{helper}{rgb} = $rgb;
  703. }
  704. $hash->{helper}->{update_timeout} = -1;
  705. #RemoveInternalTimer($hash);
  706. return $changed;
  707. }
  708. sub
  709. Aurora_Attr($$$;$)
  710. {
  711. my ($cmd, $name, $attrName, $attrVal) = @_;
  712. return;
  713. }
  714. 1;
  715. =pod
  716. =item summary nanoleaf aurora
  717. =item summary_DE nanoleaf aurora
  718. =begin html
  719. <a name="Aurora"></a>
  720. <h3>Aurora</h3>
  721. <ul>
  722. <br>
  723. <a name="Aurora_Define"></a>
  724. <b>Define</b>
  725. <ul>
  726. <code>define &lt;name&gt; Aurora &lt;ip&gt; [&lt;interval&gt;]</code><br>
  727. <br>
  728. Defines a device connected to a <a href="#Aurora">Aurora</a>.<br><br>
  729. The device status will be updated every &lt;interval&gt; seconds. 0 means no updates.
  730. Groups are updated only on definition and statusRequest<br><br>
  731. Examples:
  732. <ul>
  733. <code>define aurora Aurora 10.0.1.xxx 10</code><br>
  734. </ul>
  735. </ul><br>
  736. <a name="Aurora_Readings"></a>
  737. <b>Readings</b>
  738. <ul>
  739. <li>bri<br>
  740. the brightness reported from the device. the value can be betwen 1 and 254</li>
  741. <li>colormode<br>
  742. the current colormode</li>
  743. <li>ct<br>
  744. the colortemperature in mireds and kelvin</li>
  745. <li>hue<br>
  746. the current hue</li>
  747. <li>pct<br>
  748. the current brightness in percent</li>
  749. <li>onoff<br>
  750. the current on/off state as 0 or 1</li>
  751. <li>sat<br>
  752. the current saturation</li>
  753. <li>state<br>
  754. the current state</li>
  755. <br>
  756. Notes:
  757. <ul>
  758. <li>with current bridge firware versions groups have <code>all_on</code> and <code>any_on</code> readings,
  759. with older firmware versions groups have no readings.</li>
  760. <li>not all readings show the actual device state. all readings not related to the current colormode have to be ignored.</li>
  761. <li>the actual state of a device controlled by a living colors or living whites remote can be different and will
  762. be updated after some time.</li>
  763. </ul><br>
  764. </ul><br>
  765. <a name="Aurora_Set"></a>
  766. <b>Set</b>
  767. <ul>
  768. <li>on [&lt;ramp-time&gt;]</li>
  769. <li>off [&lt;ramp-time&gt;]</li>
  770. <li>toggle [&lt;ramp-time&gt;]</li>
  771. <li>statusRequest<br>
  772. Request device status update.</li>
  773. <li>pct &lt;value&gt; [&lt;ramp-time&gt;]<br>
  774. dim to &lt;value&gt;<br>
  775. Note: the FS20 compatible dimXX% commands are also accepted.</li>
  776. <li>color &lt;value&gt;<br>
  777. set colortemperature to &lt;value&gt; kelvin.</li>
  778. <li>bri &lt;value&gt; [&lt;ramp-time&gt;]<br>
  779. set brighness to &lt;value&gt;; range is 0-254.</li>
  780. <li>dimUp [delta]</li>
  781. <li>dimDown [delta]</li>
  782. <li>ct &lt;value&gt; [&lt;ramp-time&gt;]<br>
  783. set colortemperature to &lt;value&gt; in mireds (range is 154-500) or kelvin (rankge is 2000-6493).</li>
  784. <li>ctUp [delta]</li>
  785. <li>ctDown [delta]</li>
  786. <li>hue &lt;value&gt; [&lt;ramp-time&gt;]<br>
  787. set hue to &lt;value&gt;; range is 0-65535.</li>
  788. <li>humUp [delta]</li>
  789. <li>humDown [delta]</li>
  790. <li>sat &lt;value&gt; [&lt;ramp-time&gt;]<br>
  791. set saturation to &lt;value&gt;; range is 0-254.</li>
  792. <li>satUp [delta]</li>
  793. <li>satDown [delta]</li>
  794. <li>effect &lt;name&gt;</li>
  795. <li>rgb &lt;rrggbb&gt;<br>
  796. set the color to (the nearest equivalent of) &lt;rrggbb&gt;</li>
  797. <br>
  798. <li><a href="#setExtensions"> set extensions</a> are supported.</li>
  799. <br>
  800. Note:
  801. <ul>
  802. <li>&lt;ramp-time&gt; is given in seconds</li>
  803. <li>multiple paramters can be set at once separated by <code>:</code><br>
  804. Examples:<br>
  805. <code>set LC on : transitiontime 100</code><br>
  806. <code>set bulb on : bri 100 : color 4000</code><br></li>
  807. </ul>
  808. </ul><br>
  809. <a name="Aurora_Get"></a>
  810. <b>Get</b>
  811. <ul>
  812. <li>rgb</li>
  813. <li>RGB</li>
  814. <li>devStateIcon<br>
  815. returns html code that can be used to create an icon that represents the device color in the room overview.</li>
  816. </ul><br>
  817. <a name="Aurora_Attr"></a>
  818. <b>Attributes</b>
  819. <ul>
  820. <li>color-icon<br>
  821. 1 -> use lamp color as icon color and 100% shape as icon shape<br>
  822. 2 -> use lamp color scaled to full brightness as icon color and dim state as icon shape</li>
  823. <li>transitiontime<br>
  824. default transitiontime for all set commands if not specified directly in the set.</li>
  825. <li>delayedUpdate<br>
  826. 1 -> the update of the device status after a set command will be delayed for 1 second. usefull if multiple devices will be switched.
  827. </li>
  828. <li>devStateIcon<br>
  829. will be initialized to <code>{(Aurora_devStateIcon($name),"toggle")}</code> to show device color as default in room overview.</li>
  830. <li>webCmd<br>
  831. will be initialized to a device specific value</li>
  832. </ul>
  833. </ul><br>
  834. =end html
  835. =cut