31_Aurora.pm 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  1. # $Id: 31_Aurora.pm 15915 2018-01-17 16:44:46Z 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) = @_;
  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. } elsif( $cmd eq "transitiontime" ) {
  342. $obj->{'transitiontime'} = 0+$value;
  343. } elsif( $name && $cmd eq "delayedUpdate" ) {
  344. $defs{$name}->{helper}->{update_timeout} = 1;
  345. } elsif( $name && $cmd eq "immediateUpdate" ) {
  346. $defs{$name}->{helper}->{update_timeout} = 0;
  347. } elsif( $name && $cmd eq "noUpdate" ) {
  348. $defs{$name}->{helper}->{update_timeout} = -1;
  349. } else {
  350. return 0;
  351. }
  352. return 1;
  353. }
  354. sub
  355. Aurora_Set($@)
  356. {
  357. my ($hash, $name, @aa) = @_;
  358. my ($cmd, @args) = @aa;
  359. my %obj;
  360. $hash->{helper}->{update_timeout} = AttrVal($name, "delayedUpdate", 1);
  361. if( (my $joined = join(" ", @aa)) =~ /:/ ) {
  362. my @cmds = split(":", $joined);
  363. for( my $i = 0; $i <= $#cmds; ++$i ) {
  364. Aurora_SetParam($name, \%obj, split(" ", $cmds[$i]) );
  365. }
  366. } else {
  367. my ($cmd, $value, $value2, @a) = @aa;
  368. if( $cmd eq "statusRequest" ) {
  369. RemoveInternalTimer($hash);
  370. Aurora_GetUpdate($hash);
  371. return undef;
  372. }
  373. Aurora_SetParam($name, \%obj, $cmd, $value, $value2);
  374. }
  375. #Log 1, Dumper \%obj;
  376. if( %obj ) {
  377. if( defined($obj{on}) ) {
  378. $hash->{desired} = $obj{on}?1:0;
  379. }
  380. if( !defined($obj{transitiontime}) ) {
  381. my $transitiontime = AttrVal($name, "transitiontime", undef);
  382. $obj{transitiontime} = 0 + $transitiontime if( defined( $transitiontime ) );
  383. }
  384. }
  385. if( scalar keys %obj ) {
  386. my($err,$data) = HttpUtils_NonblockingGet({
  387. url => "http://$hash->{IP}:16021/api/v1/$attr{$name}{token}/".($obj{select}?"effects":"state"),
  388. timeout => 2,
  389. method => 'PUT',
  390. noshutdown => $hash->{noshutdown},
  391. hash => $hash,
  392. type => 'state',
  393. data => encode_json(\%obj),
  394. callback => \&Aurora_dispatch,
  395. });
  396. SetExtensionsCancel($hash);
  397. $hash->{".triggerUsed"} = 1;
  398. return undef;
  399. }
  400. my $list = "off:noArg on:noArg toggle:noArg statusRequest:noArg";
  401. $list .= " pct:colorpicker,BRI,0,1,100";
  402. $list .= " rgb:colorpicker,RGB";
  403. $list .= " color:colorpicker,CT,1200,10,6500";
  404. $list .= " hue:colorpicker,HUE,0,1,359 sat:slider,0,1,100";
  405. $list .= " dimUp:noArg dimDown:noArg";
  406. #$list .= " alert:none,select,lselect";
  407. #$list .= " dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%";
  408. if( $hash->{helper}{effects} ) {
  409. my $effects = join(',',@{$hash->{helper}{effects}});
  410. $effects =~ s/\s/#/g;
  411. $list .= " effect:,$effects";
  412. }
  413. return SetExtensions($hash, $list, $name, @aa);
  414. }
  415. sub
  416. cttorgb($)
  417. {
  418. my ($ct) = @_;
  419. # calculation from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code
  420. # adjusted by 1000K
  421. my $temp = (1000000/$ct)/100 + 10;
  422. my $r = 0;
  423. my $g = 0;
  424. my $b = 0;
  425. $r = 255;
  426. $r = 329.698727446 * ($temp - 60) ** -0.1332047592 if( $temp > 66 );
  427. $r = 0 if( $r < 0 );
  428. $r = 255 if( $r > 255 );
  429. if( $temp <= 66 ) {
  430. $g = 99.4708025861 * log($temp) - 161.1195681661;
  431. } else {
  432. $g = 288.1221695283 * ($temp - 60) ** -0.0755148492;
  433. }
  434. $g = 0 if( $g < 0 );
  435. $g = 255 if( $g > 255 );
  436. $b = 255;
  437. $b = 0 if( $temp <= 19 );
  438. if( $temp < 66 ) {
  439. $b = 138.5177312231 * log($temp-10) - 305.0447927307;
  440. }
  441. $b = 0 if( $b < 0 );
  442. $b = 255 if( $b > 255 );
  443. return( $r, $g, $b );
  444. }
  445. sub
  446. xyYtorgb($$$)
  447. {
  448. # calculation from http://www.brucelindbloom.com/index.html
  449. my ($x,$y,$Y) = @_;
  450. #Log 3, "xyY:". $x . " " . $y ." ". $Y;
  451. my $r = 0;
  452. my $g = 0;
  453. my $b = 0;
  454. if( $y > 0 ) {
  455. my $X = $x * $Y / $y;
  456. my $Z = (1 - $x - $y)*$Y / $y;
  457. if( $X > 1
  458. || $Y > 1
  459. || $Z > 1 ) {
  460. my $f = maxNum($X,$Y,$Z);
  461. $X /= $f;
  462. $Y /= $f;
  463. $Z /= $f;
  464. }
  465. #Log 3, "XYZ: ". $X . " " . $Y ." ". $Y;
  466. $r = 0.7982 * $X + 0.3389 * $Y - 0.1371 * $Z;
  467. $g = -0.5918 * $X + 1.5512 * $Y + 0.0406 * $Z;
  468. $b = 0.0008 * $X + 0.0239 * $Y + 0.9753 * $Z;
  469. if( $r > 1
  470. || $g > 1
  471. || $b > 1 ) {
  472. my $f = maxNum($r,$g,$b);
  473. $r /= $f;
  474. $g /= $f;
  475. $b /= $f;
  476. }
  477. #Log 3, "rgb: ". $r . " " . $g ." ". $b;
  478. $r *= 255;
  479. $g *= 255;
  480. $b *= 255;
  481. }
  482. return( $r, $g, $b );
  483. }
  484. sub
  485. Aurora_Get($@)
  486. {
  487. my ($hash, @a) = @_;
  488. my $name = $a[0];
  489. return "$name: get needs at least one parameter" if(@a < 2);
  490. my $cmd= $a[1];
  491. if($cmd eq "rgb") {
  492. my $r = 0;
  493. my $g = 0;
  494. my $b = 0;
  495. my $cm = ReadingsVal($name,"colormode","");
  496. if( $cm eq "ct" ) {
  497. if( ReadingsVal($name,"ct","") =~ m/(\d+)/ ) {
  498. ($r,$g,$b) = cttorgb(1000000/$1);
  499. }
  500. } else {
  501. my $h = ReadingsVal($name,"hue",0) / 359.0;
  502. my $s = ReadingsVal($name,"sat",0) / 100.0;
  503. my $v = ReadingsVal($name,"pct",0) / 100.0;
  504. ($r,$g,$b) = Color::hsv2rgb($h,$s,$v);
  505. $r *= 255;
  506. $g *= 255;
  507. $b *= 255;
  508. }
  509. return sprintf( "%02x%02x%02x", $r+0.5, $g+0.5, $b+0.5 );
  510. } elsif($cmd eq "RGB") {
  511. my $r = 0;
  512. my $g = 0;
  513. my $b = 0;
  514. my $cm = ReadingsVal($name,"colormode","");
  515. if( $cm eq "ct" ) {
  516. if( ReadingsVal($name,"ct","") =~ m/(\d+) .*/ ) {
  517. ($r,$g,$b) = cttorgb($1);
  518. }
  519. } else {
  520. my $h = ReadingsVal($name,"hue",0) / 359.0;
  521. my $s = ReadingsVal($name,"sat",0) / 100.0;
  522. my $v = 1;
  523. ($r,$g,$b) = Color::hsv2rgb($h,$s,$v);
  524. $r *= 255;
  525. $g *= 255;
  526. $b *= 255;
  527. }
  528. return sprintf( "%02x%02x%02x", $r+0.5, $g+0.5, $b+0.5 );
  529. } elsif ( $cmd eq "devStateIcon" ) {
  530. return Aurora_devStateIcon($hash);
  531. }
  532. return "Unknown argument $cmd, choose one of rgb:noArg RGB:noArg devStateIcon:noArg";
  533. }
  534. ###################################
  535. # This could be IORead in fhem, But there is none.
  536. # Read http://forum.fhem.de/index.php?t=tree&goto=54027&rid=10#msg_54027
  537. # to find out why.
  538. sub
  539. Aurora_ReadFromServer($@)
  540. {
  541. my ($hash,@a) = @_;
  542. my $name = $hash->{NAME};
  543. no strict "refs";
  544. my $ret;
  545. unshift(@a,$name);
  546. #$ret = IOWrite($hash, @a);
  547. $ret = IOWrite($hash,$hash,@a);
  548. use strict "refs";
  549. return $ret;
  550. return if(IsDummy($name) || IsIgnored($name));
  551. my $iohash = $hash->{IODev};
  552. if(!$iohash ||
  553. !$iohash->{TYPE} ||
  554. !$modules{$iohash->{TYPE}} ||
  555. !$modules{$iohash->{TYPE}}{ReadFn}) {
  556. Log3 $name, 5, "No I/O device or ReadFn found for $name";
  557. return;
  558. }
  559. no strict "refs";
  560. #my $ret;
  561. unshift(@a,$name);
  562. $ret = &{$modules{$iohash->{TYPE}}{ReadFn}}($iohash, @a);
  563. use strict "refs";
  564. return $ret;
  565. }
  566. sub
  567. Aurora_GetUpdate($)
  568. {
  569. my ($hash) = @_;
  570. my $name = $hash->{NAME};
  571. if(!$hash->{LOCAL}) {
  572. RemoveInternalTimer($hash);
  573. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "Aurora_GetUpdate", $hash, 0) if( $hash->{INTERVAL} );
  574. }
  575. return undef if(IsDisabled($name));
  576. my ($now) = gettimeofday();
  577. if( $hash->{LOCAL} || $now - $hash->{helper}{last_config_timestamp} > 300 ) {
  578. my($err,$data) = HttpUtils_NonblockingGet({
  579. url => "http://$hash->{IP}:16021/api/v1/$attr{$name}{token}",
  580. timeout => 2,
  581. method => 'GET',
  582. noshutdown => $hash->{noshutdown},
  583. hash => $hash,
  584. type => 'state',
  585. callback => \&Aurora_dispatch,
  586. });
  587. $hash->{helper}{last_config_timestamp} = $now;
  588. } else {
  589. my($err,$data) = HttpUtils_NonblockingGet({
  590. url => "http://$hash->{IP}:16021/api/v1/$attr{$name}{token}/state",
  591. timeout => 2,
  592. method => 'GET',
  593. noshutdown => $hash->{noshutdown},
  594. hash => $hash,
  595. type => 'state',
  596. callback => \&Aurora_dispatch,
  597. });
  598. }
  599. return undef;
  600. }
  601. sub
  602. AuroraSetIcon($;$)
  603. {
  604. my ($hash,$force) = @_;
  605. $hash = $defs{$hash} if( ref($hash) ne 'HASH' );
  606. return undef if( !$hash );
  607. my $name = $hash->{NAME};
  608. return if( defined($attr{$name}{icon}) && !$force );
  609. }
  610. sub
  611. Aurora_Parse($$)
  612. {
  613. my($hash,$result) = @_;
  614. my $name = $hash->{NAME};
  615. if( ref($result) ne "HASH" ) {
  616. if( ref($result) && $Aurora_hasDataDumper) {
  617. Log3 $name, 2, "$name: got wrong status message for $name: ". Dumper $result;
  618. } else {
  619. Log3 $name, 2, "$name: got wrong status message for $name: $result";
  620. }
  621. return undef;
  622. }
  623. Log3 $name, 4, "parse status message for $name";
  624. Log3 $name, 5, Dumper $result if($Aurora_hasDataDumper);
  625. $hash->{name} = $result->{name} if( defined($result->{name}) );
  626. $hash->{serialNo} = $result->{serialNo} if( defined($result->{serialNo}) );
  627. $hash->{manufacturer} = $result->{manufacturer} if( defined($result->{manufacturer}) );
  628. $hash->{model} = $result->{model} if( defined($result->{model}) );
  629. $hash->{firmwareVersion} = $result->{firmwareVersion} if( defined($result->{firmwareVersion}) );
  630. if( my $effects = $result->{effects} ) {
  631. $hash->{helper}{effects} = $effects->{effectsList} if( defined($effects->{effectsList}) );
  632. if( my $effect = $effects->{select} ) {
  633. if( $effect ne $hash->{helper}{effect} ) { readingsSingleUpdate($hash, 'effect', $effect, 1 ) };
  634. $hash->{helper}{effect} = $effect;
  635. }
  636. }
  637. $attr{$name}{devStateIcon} = '{(Aurora_devStateIcon($name),"toggle")}' if( !defined( $attr{$name}{devStateIcon} ) );
  638. if( !defined($attr{$name}{webCmd}) ) {
  639. $attr{$name}{webCmd} = 'rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:ct 490:ct 380:ct 270:ct 160:effect:on:off';
  640. #$attr{$name}{webCmd} = 'hue:rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:toggle:on:off';
  641. #$attr{$name}{webCmd} = 'ct:ct 490:ct 380:ct 270:ct 160:toggle:on:off';
  642. #$attr{$name}{webCmd} = 'pct:toggle:on:off';
  643. #$attr{$name}{webCmd} = 'toggle:on:off';
  644. }
  645. readingsBeginUpdate($hash);
  646. my $state = $result;
  647. $state = $state->{'state'} if( defined($state->{'state'}) );
  648. my $on = $state->{on}{value};
  649. $on = $hash->{helper}{on} if( !defined($on) );
  650. my $colormode = $state->{'colorMode'};
  651. my $pct = $state->{'brightness'}{value};
  652. $pct = $hash->{helper}{pct} if( !defined($pct) );
  653. my $ct = $state->{'ct'}{value};
  654. my $hue = $state->{'hue'}{value};
  655. my $sat = $state->{'sat'}{value};
  656. my $alert = $state->{alert};
  657. my $effect = $state->{effect};
  658. if( defined($colormode) && $colormode ne $hash->{helper}{colormode} ) {readingsBulkUpdate($hash,"colormode",$colormode);}
  659. if( defined($ct) && $ct != $hash->{helper}{ct} ) {
  660. if( $ct == 0 ) {
  661. readingsBulkUpdate($hash,"ct",$ct);
  662. }
  663. else {
  664. readingsBulkUpdate($hash,"ct",$ct);
  665. }
  666. }
  667. if( defined($hue) && $hue != $hash->{helper}{hue} ) {readingsBulkUpdate($hash,"hue",$hue);}
  668. if( defined($sat) && $sat != $hash->{helper}{sat} ) {readingsBulkUpdate($hash,"sat",$sat);}
  669. if( defined($alert) && $alert ne $hash->{helper}{alert} ) {readingsBulkUpdate($hash,"alert",$alert);}
  670. if( defined($effect) && $effect ne $hash->{helper}{effect} ) {readingsBulkUpdate($hash,"effect",$effect);}
  671. my $s = '';
  672. if( $on )
  673. {
  674. $s = 'on';
  675. if( $on != $hash->{helper}{on} ) {readingsBulkUpdate($hash,"onoff",1);}
  676. $s = 'off' if( $pct == 0 );
  677. }
  678. else
  679. {
  680. $on = 0;
  681. $s = 'off';
  682. $pct = 0;
  683. if( $on != $hash->{helper}{on} ) {readingsBulkUpdate($hash,"onoff",0);}
  684. }
  685. if( $pct != $hash->{helper}{pct} ) {readingsBulkUpdate($hash,"pct", $pct);}
  686. #if( $pct != $hash->{helper}{pct} ) {readingsBulkUpdate($hash,"level", $pct . ' %');}
  687. $hash->{helper}{on} = $on if( defined($on) );
  688. $hash->{helper}{colormode} = $colormode if( defined($colormode) );
  689. $hash->{helper}{ct} = $ct if( defined($ct) );
  690. $hash->{helper}{hue} = $hue if( defined($hue) );
  691. $hash->{helper}{sat} = $sat if( defined($sat) );
  692. $hash->{helper}{alert} = $alert if( defined($alert) );
  693. $hash->{helper}{effect} = $effect if( defined($effect) );
  694. $hash->{helper}{pct} = $pct;
  695. my $changed = $hash->{CHANGED}?1:0;
  696. if( $s ne $hash->{STATE} ) {readingsBulkUpdate($hash,"state",$s);}
  697. readingsEndUpdate($hash,1);
  698. if( defined($colormode) ) {
  699. my $rgb = CommandGet("","$name rgb");
  700. if( $rgb ne $hash->{helper}{rgb} ) { readingsSingleUpdate($hash,"rgb", $rgb,1); };
  701. $hash->{helper}{rgb} = $rgb;
  702. }
  703. $hash->{helper}->{update_timeout} = -1;
  704. #RemoveInternalTimer($hash);
  705. return $changed;
  706. }
  707. sub
  708. Aurora_Attr($$$;$)
  709. {
  710. my ($cmd, $name, $attrName, $attrVal) = @_;
  711. return;
  712. }
  713. 1;
  714. =pod
  715. =item summary nanoleaf aurora
  716. =item summary_DE nanoleaf aurora
  717. =begin html
  718. <a name="Aurora"></a>
  719. <h3>Aurora</h3>
  720. <ul>
  721. <br>
  722. <a name="Aurora_Define"></a>
  723. <b>Define</b>
  724. <ul>
  725. <code>define &lt;name&gt; Aurora &lt;ip&gt; [&lt;interval&gt;]</code><br>
  726. <br>
  727. Defines a device connected to a <a href="#Aurora">Aurora</a>.<br><br>
  728. The device status will be updated every &lt;interval&gt; seconds. 0 means no updates.
  729. Groups are updated only on definition and statusRequest<br><br>
  730. Examples:
  731. <ul>
  732. <code>define aurora Aurora 10.0.1.xxx 10</code><br>
  733. </ul>
  734. </ul><br>
  735. <a name="Aurora_Readings"></a>
  736. <b>Readings</b>
  737. <ul>
  738. <li>bri<br>
  739. the brightness reported from the device. the value can be betwen 1 and 254</li>
  740. <li>colormode<br>
  741. the current colormode</li>
  742. <li>ct<br>
  743. the colortemperature in mireds and kelvin</li>
  744. <li>hue<br>
  745. the current hue</li>
  746. <li>pct<br>
  747. the current brightness in percent</li>
  748. <li>onoff<br>
  749. the current on/off state as 0 or 1</li>
  750. <li>sat<br>
  751. the current saturation</li>
  752. <li>state<br>
  753. the current state</li>
  754. <br>
  755. Notes:
  756. <ul>
  757. <li>with current bridge firware versions groups have <code>all_on</code> and <code>any_on</code> readings,
  758. with older firmware versions groups have no readings.</li>
  759. <li>not all readings show the actual device state. all readings not related to the current colormode have to be ignored.</li>
  760. <li>the actual state of a device controlled by a living colors or living whites remote can be different and will
  761. be updated after some time.</li>
  762. </ul><br>
  763. </ul><br>
  764. <a name="Aurora_Set"></a>
  765. <b>Set</b>
  766. <ul>
  767. <li>on [&lt;ramp-time&gt;]</li>
  768. <li>off [&lt;ramp-time&gt;]</li>
  769. <li>toggle [&lt;ramp-time&gt;]</li>
  770. <li>statusRequest<br>
  771. Request device status update.</li>
  772. <li>pct &lt;value&gt; [&lt;ramp-time&gt;]<br>
  773. dim to &lt;value&gt;<br>
  774. Note: the FS20 compatible dimXX% commands are also accepted.</li>
  775. <li>color &lt;value&gt;<br>
  776. set colortemperature to &lt;value&gt; kelvin.</li>
  777. <li>bri &lt;value&gt; [&lt;ramp-time&gt;]<br>
  778. set brighness to &lt;value&gt;; range is 0-254.</li>
  779. <li>dimUp [delta]</li>
  780. <li>dimDown [delta]</li>
  781. <li>ct &lt;value&gt; [&lt;ramp-time&gt;]<br>
  782. set colortemperature to &lt;value&gt; in mireds (range is 154-500) or kelvin (rankge is 2000-6493).</li>
  783. <li>ctUp [delta]</li>
  784. <li>ctDown [delta]</li>
  785. <li>hue &lt;value&gt; [&lt;ramp-time&gt;]<br>
  786. set hue to &lt;value&gt;; range is 0-65535.</li>
  787. <li>humUp [delta]</li>
  788. <li>humDown [delta]</li>
  789. <li>sat &lt;value&gt; [&lt;ramp-time&gt;]<br>
  790. set saturation to &lt;value&gt;; range is 0-254.</li>
  791. <li>satUp [delta]</li>
  792. <li>satDown [delta]</li>
  793. <li>effect &lt;name&gt;</li>
  794. <li>rgb &lt;rrggbb&gt;<br>
  795. set the color to (the nearest equivalent of) &lt;rrggbb&gt;</li>
  796. <br>
  797. <li><a href="#setExtensions"> set extensions</a> are supported.</li>
  798. <br>
  799. Note:
  800. <ul>
  801. <li>&lt;ramp-time&gt; is given in seconds</li>
  802. <li>multiple paramters can be set at once separated by <code>:</code><br>
  803. Examples:<br>
  804. <code>set LC on : transitiontime 100</code><br>
  805. <code>set bulb on : bri 100 : color 4000</code><br></li>
  806. </ul>
  807. </ul><br>
  808. <a name="Aurora_Get"></a>
  809. <b>Get</b>
  810. <ul>
  811. <li>rgb</li>
  812. <li>RGB</li>
  813. <li>devStateIcon<br>
  814. returns html code that can be used to create an icon that represents the device color in the room overview.</li>
  815. </ul><br>
  816. <a name="Aurora_Attr"></a>
  817. <b>Attributes</b>
  818. <ul>
  819. <li>color-icon<br>
  820. 1 -> use lamp color as icon color and 100% shape as icon shape<br>
  821. 2 -> use lamp color scaled to full brightness as icon color and dim state as icon shape</li>
  822. <li>transitiontime<br>
  823. default transitiontime for all set commands if not specified directly in the set.</li>
  824. <li>delayedUpdate<br>
  825. 1 -> the update of the device status after a set command will be delayed for 1 second. usefull if multiple devices will be switched.
  826. </li>
  827. <li>devStateIcon<br>
  828. will be initialized to <code>{(Aurora_devStateIcon($name),"toggle")}</code> to show device color as default in room overview.</li>
  829. <li>webCmd<br>
  830. will be initialized to a device specific value</li>
  831. </ul>
  832. </ul><br>
  833. =end html
  834. =cut