31_Aurora.pm 27 KB

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