30_HUEBridge.pm 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950
  1. # $Id: 30_HUEBridge.pm 16310 2018-03-02 10:43:36Z justme1968 $
  2. # "Hue Personal Wireless Lighting" is a trademark owned by Koninklijke Philips Electronics N.V.,
  3. # see www.meethue.com for more information.
  4. # I am in no way affiliated with the Philips organization.
  5. package main;
  6. use strict;
  7. use warnings;
  8. use POSIX;
  9. use JSON;
  10. use Data::Dumper;
  11. use HttpUtils;
  12. use IO::Socket::INET;
  13. sub HUEBridge_Initialize($)
  14. {
  15. my ($hash) = @_;
  16. # Provider
  17. $hash->{ReadFn} = "HUEBridge_Read";
  18. $hash->{WriteFn} = "HUEBridge_Write";
  19. $hash->{Clients} = ":HUEDevice:";
  20. #Consumer
  21. $hash->{DefFn} = "HUEBridge_Define";
  22. $hash->{NotifyFn} = "HUEBridge_Notify";
  23. $hash->{SetFn} = "HUEBridge_Set";
  24. $hash->{GetFn} = "HUEBridge_Get";
  25. $hash->{AttrFn} = "HUEBridge_Attr";
  26. $hash->{UndefFn} = "HUEBridge_Undefine";
  27. $hash->{AttrList} = "key disable:1 disabledForIntervals createGroupReadings:1,0 httpUtils:1,0 noshutdown:1,0 pollDevices:1,2,0 queryAfterSet:1,0 $readingFnAttributes";
  28. }
  29. sub
  30. HUEBridge_Read($)
  31. {
  32. my ($hash) = @_;
  33. my $name = $hash->{NAME};
  34. my $buf;
  35. my $len = sysread($hash->{CD}, $buf, 10240);
  36. my $peerhost = $hash->{CD}->peerhost;
  37. my $peerport = $hash->{CD}->peerport;
  38. my $close = 0;
  39. if( !defined($len) || !$len ) {
  40. $close = 1;
  41. } elsif( $hash->{websocket} ) {
  42. $hash->{buf} .= $buf;
  43. do {
  44. my $fin = (ord(substr($hash->{buf},0,1)) & 0x80)?1:0;
  45. my $op = (ord(substr($hash->{buf},0,1)) & 0x0F);
  46. my $mask = (ord(substr($hash->{buf},1,1)) & 0x80)?1:0;
  47. my $len = (ord(substr($hash->{buf},1,1)) & 0x7F);
  48. my $i = 2;
  49. if( $len == 126 ) {
  50. $len = unpack( 'n', substr($hash->{buf},$i,2) );
  51. $i += 2;
  52. } elsif( $len == 127 ) {
  53. $len = unpack( 'q', substr($hash->{buf},$i,8) );
  54. $i += 8;
  55. }
  56. if( $mask ) {
  57. $mask = substr($hash->{buf},$i,4);
  58. $i += 4;
  59. }
  60. #FIXME: hande !$fin
  61. return if( $len > length($hash->{buf})-$i );
  62. my $data = substr($hash->{buf}, $i, $len);
  63. $hash->{buf} = substr($hash->{buf},$i+$len);
  64. #Log 1, ">>>$data<<<";
  65. if( $data eq '?' ) {
  66. #ignore keepalive
  67. } elsif( $op == 0x01 ) {
  68. my $obj = eval { decode_json($data) };
  69. if( $obj ) {
  70. Log3 $name, 5, "$name: websocket data: ". Dumper $obj;
  71. } else {
  72. Log3 $name, 2, "$name: unhandled websocket text $data";
  73. }
  74. if( $obj->{t} eq 'event' && $obj->{e} eq 'changed' ) {
  75. my $code;
  76. my $id = $obj->{id};
  77. $code = $name ."-". $id if( $obj->{r} eq 'lights' );
  78. $code = $name ."-S". $id if( $obj->{r} eq 'sensors' );
  79. $code = $name ."-G". $id if( $obj->{r} eq 'groups' );
  80. if( !$code ) {
  81. Log3 $name, 5, "$name: ignoring event: $code";
  82. return;
  83. }
  84. my $chash = $modules{HUEDevice}{defptr}{$code};
  85. if( defined($chash) ) {
  86. HUEDevice_Parse($chash,$obj);
  87. HUEBridge_updateGroups($hash, $chash->{ID}) if( !$chash->{helper}{devtype} );
  88. } else {
  89. Log3 $name, 4, "$name: message for unknow device received: $code";
  90. }
  91. } elsif( $obj->{t} eq 'event' && $obj->{e} eq 'scene-called' ) {
  92. Log3 $name, 5, "$name: todo: handle websocket scene-called $data";
  93. # trigger scene event ?
  94. } elsif( $obj->{t} eq 'event' && $obj->{e} eq 'added' ) {
  95. Log3 $name, 5, "$name: websocket add: $data";
  96. HUEBridge_Autocreate($hash);
  97. } elsif( $obj->{t} eq 'event' && $obj->{e} eq 'deleted' ) {
  98. Log3 $name, 5, "$name: todo: handle websocket delete $data";
  99. # do what ?
  100. } else {
  101. Log3 $name, 5, "$name: unknown websocket data: $data";
  102. }
  103. } else {
  104. Log3 $name, 2, "$name: unhandled websocket data: $data";
  105. }
  106. } while( $hash->{buf} && !$close );
  107. } elsif( $buf =~ m'^HTTP/1.1 101 Switching Protocols'i ) {
  108. $hash->{websocket} = 1;
  109. #my $buf = plex_msg2hash($buf, 1);
  110. #Log 1, $buf;
  111. Log3 $name, 3, "$name: websocket: Switching Protocols ok";
  112. } else {
  113. #Log 1, $buf;
  114. $close = 1;
  115. Log3 $name, 2, "$name: websocket: Switching Protocols failed";
  116. }
  117. if( $close ) {
  118. HUEBridge_closeWebsocket($hash);
  119. Log3 $name, 2, "$name: websocket closed";
  120. }
  121. }
  122. sub
  123. HUEBridge_Write($@)
  124. {
  125. my ($hash,$chash,$name,$id,$obj)= @_;
  126. return HUEBridge_Call($hash, $chash, 'groups/' . $1, $obj) if( $id =~ m/^G(\d.*)/ );
  127. return HUEBridge_Call($hash, $chash, 'sensors/' . $1, $obj) if( $id =~ m/^S(\d.*)/ );
  128. return HUEBridge_Call($hash, $chash, 'lights/' . $id, $obj);
  129. }
  130. sub
  131. HUEBridge_Detect($)
  132. {
  133. my ($hash) = @_;
  134. my $name = $hash->{NAME};
  135. Log3 $name, 3, "HUEBridge_Detect";
  136. my ($err,$ret) = HttpUtils_BlockingGet({
  137. url => "http://www.meethue.com/api/nupnp",
  138. method => "GET",
  139. });
  140. if( defined($err) && $err ) {
  141. Log3 $name, 3, "HUEBridge_Detect: error detecting bridge: ".$err;
  142. return;
  143. }
  144. my $host = '';
  145. if( defined($ret) && $ret ne '' && $ret =~ m/^[\[{].*[\]}]$/ ) {
  146. my $obj = eval { decode_json($ret) };
  147. Log3 $name, 2, "$name: json error: $@ in $ret" if( $@ );
  148. if( defined($obj->[0])
  149. && defined($obj->[0]->{'internalipaddress'}) ) {
  150. $host = $obj->[0]->{'internalipaddress'};
  151. }
  152. }
  153. if( !defined($host) || $host eq '' ) {
  154. Log3 $name, 3, 'HUEBridge_Detect: error detecting bridge.';
  155. return;
  156. }
  157. Log3 $name, 3, "HUEBridge_Detect: ${host}";
  158. $hash->{host} = $host;
  159. return $host;
  160. }
  161. sub
  162. HUEBridge_Define($$)
  163. {
  164. my ($hash, $def) = @_;
  165. my @args = split("[ \t]+", $def);
  166. return "Usage: define <name> HUEBridge [<host>] [interval]" if(@args < 2);
  167. my ($name, $type, $host, $interval) = @args;
  168. if( !defined($host) ) {
  169. $hash->{NUPNP} = 1;
  170. HUEBridge_Detect($hash);
  171. } else {
  172. delete $hash->{NUPNP};
  173. }
  174. $interval= 60 unless defined($interval);
  175. if( $interval < 10 ) { $interval = 10; }
  176. readingsSingleUpdate($hash, 'state', 'initialized', 1 );
  177. $hash->{host} = $host;
  178. $hash->{INTERVAL} = $interval;
  179. $attr{$name}{"key"} = join "",map { unpack "H*", chr(rand(256)) } 1..16 unless defined( AttrVal($name, "key", undef) );
  180. $hash->{helper}{last_config_timestamp} = 0;
  181. if( !defined($hash->{helper}{count}) ) {
  182. $modules{$hash->{TYPE}}{helper}{count} = 0 if( !defined($modules{$hash->{TYPE}}{helper}{count}) );
  183. $hash->{helper}{count} = $modules{$hash->{TYPE}}{helper}{count}++;
  184. }
  185. $hash->{NOTIFYDEV} = "global";
  186. if( $init_done ) {
  187. HUEBridge_OpenDev( $hash ) if( !IsDisabled($name) );
  188. }
  189. return undef;
  190. }
  191. sub
  192. HUEBridge_Notify($$)
  193. {
  194. my ($hash,$dev) = @_;
  195. my $name = $hash->{NAME};
  196. my $type = $hash->{TYPE};
  197. return if($dev->{NAME} ne "global");
  198. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  199. if( IsDisabled($name) > 0 ) {
  200. readingsSingleUpdate($hash, 'state', 'inactive', 1 ) if( ReadingsVal($name,'inactive','' ) ne 'disabled' );
  201. return undef;
  202. }
  203. HUEBridge_OpenDev($hash);
  204. return undef;
  205. }
  206. sub HUEBridge_Undefine($$)
  207. {
  208. my ($hash,$arg) = @_;
  209. RemoveInternalTimer($hash);
  210. return undef;
  211. }
  212. sub
  213. HUEBridge_hash2header($)
  214. {
  215. my ($hash) = @_;
  216. return $hash if( ref($hash) ne 'HASH' );
  217. my $header;
  218. foreach my $key (keys %{$hash}) {
  219. #$header .= "\r\n" if( $header );
  220. $header .= "$key: $hash->{$key}\r\n";
  221. }
  222. return $header;
  223. }
  224. sub HUEBridge_closeWebsocket($)
  225. {
  226. my ($hash) = @_;
  227. my $name = $hash->{NAME};
  228. delete $hash->{buf};
  229. delete $hash->{websocket};
  230. close($hash->{CD}) if( defined($hash->{CD}) );
  231. delete($hash->{CD});
  232. delete($selectlist{$name});
  233. delete($hash->{FD});
  234. delete($hash->{PORT});
  235. }
  236. sub HUEBridge_openWebsocket($)
  237. {
  238. my ($hash) = @_;
  239. my $name = $hash->{NAME};
  240. return if( !defined($hash->{websocketport}) );
  241. HUEBridge_closeWebsocket($hash);
  242. my ($host,undef) = split(':',$hash->{host},2);
  243. if( my $socket = IO::Socket::INET->new(PeerAddr=>"$host:$hash->{websocketport}", Timeout=>2, Blocking=>1, ReuseAddr=>1) ) {
  244. $hash->{CD} = $socket;
  245. $hash->{FD} = $socket->fileno();
  246. $hash->{PORT} = $socket->sockport if( $socket->sockport );
  247. $selectlist{$name} = $hash;
  248. Log3 $name, 3, "$name: websocket opened to $host:$hash->{websocketport}";
  249. my $ret = "GET ws://$host:$hash->{websocketport} HTTP/1.1\r\n";
  250. $ret .= HUEBridge_hash2header( { 'Host' => "$host:$hash->{websocketport}",
  251. 'Upgrade' => 'websocket',
  252. 'Connection' => 'Upgrade',
  253. 'Pragma' => 'no-cache',
  254. 'Cache-Control' => 'no-cache',
  255. 'Sec-WebSocket-Key' => 'RkhFTQ==',
  256. 'Sec-WebSocket-Version' => '13',
  257. } );
  258. $ret .= "\r\n";
  259. #Log 1, $ret;
  260. syswrite($hash->{CD}, $ret );
  261. } else {
  262. Log3 $name, 2, "$name: failed to open websocket";
  263. }
  264. }
  265. sub HUEBridge_fillBridgeInfo($$)
  266. {
  267. my ($hash,$config) = @_;
  268. my $name = $hash->{NAME};
  269. $hash->{name} = $config->{name};
  270. $hash->{modelid} = $config->{modelid};
  271. $hash->{swversion} = $config->{swversion};
  272. $hash->{apiversion} = $config->{apiversion};
  273. if( defined($config->{websocketport}) ) {
  274. $hash->{websocketport} = $config->{websocketport};
  275. HUEBridge_openWebsocket($hash);
  276. }
  277. if( $hash->{apiversion} ) {
  278. my @l = split( '\.', $config->{apiversion} );
  279. $hash->{helper}{apiversion} = ($l[0] << 16) + ($l[1] << 8) + $l[2];
  280. }
  281. if( !defined($config->{'linkbutton'})
  282. && !defined($attr{$name}{icon}) ) {
  283. $attr{$name}{icon} = 'hue_filled_bridge_v1' if( $hash->{modelid} && $hash->{modelid} eq 'BSB001' );
  284. $attr{$name}{icon} = 'hue_filled_bridge_v2' if( $hash->{modelid} && $hash->{modelid} eq 'BSB002' );
  285. }
  286. }
  287. sub
  288. HUEBridge_OpenDev($)
  289. {
  290. my ($hash) = @_;
  291. my $name = $hash->{NAME};
  292. HUEBridge_Detect($hash) if( defined($hash->{NUPNP}) );
  293. my ($err,$ret) = HttpUtils_BlockingGet({
  294. url => "http://$hash->{host}/description.xml",
  295. method => "GET",
  296. timeout => 3,
  297. });
  298. if( defined($err) && $err ) {
  299. Log3 $name, 2, "HUEBridge_OpenDev: error reading description: ". $err;
  300. } else {
  301. Log3 $name, 5, "HUEBridge_OpenDev: got description: $ret";
  302. $ret =~ m/<modelName>([^<]*)/;
  303. $hash->{modelName} = $1;
  304. $ret =~ m/<manufacturer>([^<]*)/;
  305. $hash->{manufacturer} = $1;
  306. }
  307. my $result = HUEBridge_Call($hash, undef, 'config', undef);
  308. if( !defined($result) ) {
  309. Log3 $name, 2, "HUEBridge_OpenDev: got empty config";
  310. return undef;
  311. }
  312. Log3 $name, 5, "HUEBridge_OpenDev: got config " . Dumper $result;
  313. if( !defined($result->{'linkbutton'}) || !AttrVal($name, 'key', undef) )
  314. {
  315. HUEBridge_fillBridgeInfo($hash, $result);
  316. HUEBridge_Pair($hash);
  317. return;
  318. }
  319. $hash->{mac} = $result->{'mac'};
  320. readingsSingleUpdate($hash, 'state', 'connected', 1 );
  321. HUEBridge_GetUpdate($hash);
  322. HUEBridge_Autocreate($hash);
  323. return undef;
  324. }
  325. sub HUEBridge_Pair($)
  326. {
  327. my ($hash) = @_;
  328. my $name = $hash->{NAME};
  329. readingsSingleUpdate($hash, 'state', 'pairing', 1 );
  330. my $result = HUEBridge_Register($hash);
  331. if( $result->{'error'} )
  332. {
  333. RemoveInternalTimer($hash);
  334. InternalTimer(gettimeofday()+5, "HUEBridge_Pair", $hash, 0);
  335. return undef;
  336. }
  337. $attr{$name}{key} = $result->{success}{username} if( $result->{success}{username} );
  338. readingsSingleUpdate($hash, 'state', 'paired', 1 );
  339. HUEBridge_OpenDev($hash);
  340. return undef;
  341. }
  342. sub
  343. HUEBridge_string2array($)
  344. {
  345. my ($lights) = @_;
  346. my %lights = ();
  347. foreach my $part ( split(',', $lights) ) {
  348. my $light = $part;
  349. $light = $defs{$light}{ID} if( defined $defs{$light} && $defs{$light}{TYPE} eq 'HUEDevice' );
  350. if( $light =~ m/^G/ ) {
  351. my $lights = $defs{$part}->{lights};
  352. if( $lights ) {
  353. foreach my $light ( split(',', $lights) ) {
  354. $lights{$light} = 1;
  355. }
  356. }
  357. } else {
  358. $lights{$light} = 1;
  359. }
  360. }
  361. my @lights = sort {$a<=>$b} keys(%lights);
  362. return \@lights;
  363. }
  364. sub
  365. HUEBridge_Set($@)
  366. {
  367. my ($hash, $name, $cmd, @args) = @_;
  368. my ($arg, @params) = @args;
  369. $hash->{".triggerUsed"} = 1;
  370. return "$name: not paired" if( ReadingsVal($name, 'state', '' ) =~ m/^link/ );
  371. #return "$name: not connected" if( $hash->{STATE} ne 'connected' );
  372. # usage check
  373. if($cmd eq 'statusRequest') {
  374. return "usage: statusRequest" if( @args != 0 );
  375. $hash->{LOCAL} = 1;
  376. #RemoveInternalTimer($hash);
  377. HUEBridge_GetUpdate($hash);
  378. delete $hash->{LOCAL};
  379. return undef;
  380. } elsif($cmd eq 'swupdate') {
  381. return "usage: swupdate" if( @args != 0 );
  382. my $obj = {
  383. 'swupdate' => { 'updatestate' => 3, },
  384. };
  385. my $result = HUEBridge_Call($hash, undef, 'config', $obj);
  386. if( !defined($result) || $result->{'error'} ) {
  387. return $result->{'error'}->{'description'};
  388. }
  389. $hash->{updatestate} = 3;
  390. $hash->{helper}{updatestate} = $hash->{updatestate};
  391. readingsSingleUpdate($hash, 'state', 'updating', 1 );
  392. return "starting update";
  393. } elsif($cmd eq 'autocreate') {
  394. return "usage: autocreate" if( @args != 0 );
  395. return HUEBridge_Autocreate($hash,1);
  396. } elsif($cmd eq 'autodetect') {
  397. return "usage: autodetect" if( @args != 0 );
  398. my $result = HUEBridge_Call($hash, undef, 'lights', undef, 'POST');
  399. return $result->{error}{description} if( $result->{error} );
  400. return $result->{success}{'/lights'} if( $result->{success} );
  401. return undef;
  402. } elsif($cmd eq 'delete') {
  403. return "usage: delete <id>" if( @args != 1 );
  404. if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) {
  405. $arg = $defs{$arg}{ID};
  406. }
  407. return "$arg is not a hue light number" if( $arg !~ m/^\d+$/ );
  408. my $code = $name ."-". $arg;
  409. if( defined($modules{HUEDevice}{defptr}{$code}) ) {
  410. CommandDelete( undef, "$modules{HUEDevice}{defptr}{$code}{NAME}" );
  411. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  412. }
  413. my $result = HUEBridge_Call($hash, undef, "lights/$arg", undef, 'DELETE');
  414. return $result->{error}{description} if( $result->{error} );
  415. return undef;
  416. } elsif($cmd eq 'creategroup') {
  417. return "usage: creategroup <name> <lights>" if( @args < 2 );
  418. my $obj = { 'name' => join( ' ', @args[0..@args-2]),
  419. 'lights' => HUEBridge_string2array($args[@args-1]),
  420. };
  421. my $result = HUEBridge_Call($hash, undef, 'groups', $obj, 'POST');
  422. return $result->{error}{description} if( $result->{error} );
  423. if( $result->{success} ) {
  424. HUEBridge_Autocreate($hash);
  425. my $code = $name ."-G". $result->{success}{id};
  426. return "created $modules{HUEDevice}{defptr}{$code}->{NAME}" if( defined($modules{HUEDevice}{defptr}{$code}) );
  427. }
  428. return undef;
  429. } elsif($cmd eq 'deletegroup') {
  430. return "usage: deletegroup <id>" if( @args != 1 );
  431. if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) {
  432. return "$arg is not a hue group" if( $defs{$arg}{ID} != m/^G/ );
  433. $defs{$arg}{ID} =~ m/G(.*)/;
  434. $arg = $1;
  435. }
  436. my $code = $name ."-G". $arg;
  437. if( defined($modules{HUEDevice}{defptr}{$code}) ) {
  438. CommandDelete( undef, "$modules{HUEDevice}{defptr}{$code}{NAME}" );
  439. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  440. }
  441. return "$arg is not a hue group number" if( $arg !~ m/^\d+$/ );
  442. my $result = HUEBridge_Call($hash, undef, "groups/$arg", undef, 'DELETE');
  443. return $result->{error}{description} if( $result->{error} );
  444. return undef;
  445. } elsif($cmd eq 'savescene') {
  446. my $result;
  447. if( $hash->{helper}{apiversion} && $hash->{helper}{apiversion} >= (1<<16) + (11<<8) ) {
  448. return "usage: savescene <name> <lights>" if( @args < 2 );
  449. my $obj = { 'name' => join( ' ', @args[0..@args-2]),
  450. 'recycle' => JSON::true,
  451. 'lights' => HUEBridge_string2array($args[@args-1]),
  452. };
  453. $result = HUEBridge_Call($hash, undef, "scenes", $obj, 'POST');
  454. } else {
  455. return "usage: savescene <id> <name> <lights>" if( @args < 3 );
  456. my $obj = { 'name' => join( ' ', @args[1..@args-2]),
  457. 'lights' => HUEBridge_string2array($args[@args-1]),
  458. };
  459. $result = HUEBridge_Call($hash, undef, "scenes/$arg", $obj, 'PUT');
  460. }
  461. return $result->{error}{description} if( $result->{error} );
  462. if( $result->{success} ) {
  463. return "created $result->{success}{id}" if( $result->{success}{id} );
  464. return "created $arg";
  465. }
  466. return undef;
  467. } elsif($cmd eq 'modifyscene') {
  468. return "usage: modifyscene <id> <light> <light args>" if( @args < 3 );
  469. my( $light, @aa ) = @params;
  470. $light = $defs{$light}{ID} if( defined $defs{$light} && $defs{$light}{TYPE} eq 'HUEDevice' );
  471. my %obj;
  472. if( (my $joined = join(" ", @aa)) =~ /:/ ) {
  473. my @cmds = split(":", $joined);
  474. for( my $i = 0; $i <= $#cmds; ++$i ) {
  475. HUEDevice_SetParam(undef, \%obj, split(" ", $cmds[$i]) );
  476. }
  477. } else {
  478. my ($cmd, $value, $value2, @a) = @aa;
  479. HUEDevice_SetParam(undef, \%obj, $cmd, $value, $value2);
  480. }
  481. my $result;
  482. if( $hash->{helper}{apiversion} && $hash->{helper}{apiversion} >= (1<<16) + (11<<8) ) {
  483. $result = HUEBridge_Call($hash, undef, "scenes/$arg/lightstates/$light", \%obj, 'PUT');
  484. } else {
  485. $result = HUEBridge_Call($hash, undef, "scenes/$arg/lights/$light/state", \%obj, 'PUT');
  486. }
  487. return $result->{error}{description} if( $result->{error} );
  488. return undef;
  489. } elsif($cmd eq 'deletescene') {
  490. return "usage: deletescene <id>" if( @args != 1 );
  491. my $result = HUEBridge_Call($hash, undef, "scenes/$arg", undef, 'DELETE');
  492. return $result->{error}{description} if( $result->{error} );
  493. return undef;
  494. } elsif($cmd eq 'scene') {
  495. return "usage: scene <id>" if( @args != 1 );
  496. my $obj = { 'scene' => $arg };
  497. my $result = HUEBridge_Call($hash, undef, "groups/0/action", $obj, 'PUT');
  498. return $result->{error}{description} if( $result->{error} );
  499. RemoveInternalTimer($hash);
  500. InternalTimer(gettimeofday()+10, "HUEBridge_GetUpdate", $hash, 0);
  501. return undef;
  502. } elsif($cmd eq 'createrule' || $cmd eq 'updaterule') {
  503. return "usage: createrule <name> <conditions&actions json>" if( $cmd eq 'createrule' && @args < 2 );
  504. return "usage: updaterule <id> <conditions&actions json>" if( $cmd eq 'updaterule' && @args != 2 );
  505. $args[@args-1] = '
  506. { "name":"Wall Switch Rule",
  507. "conditions":[
  508. {"address":"/sensors/1/state/lastupdated","operator":"dx"}
  509. ],
  510. "actions":[
  511. {"address":"/groups/0/action","method":"PUT", "body":{"scene":"S3"}}
  512. ]}' if( 0 || !$args[@args-1] );
  513. my $json = $args[@args-1];
  514. my $obj = eval { decode_json($json) };
  515. if( $@ ) {
  516. Log3 $name, 2, "$name: json error: $@ in $json";
  517. return undef;
  518. }
  519. my $result;
  520. if( $cmd eq 'updaterule' ) {
  521. $result = HUEBridge_Call($hash, undef, "rules/$args[0]", $obj, 'PUT');
  522. } else {
  523. $obj->{name} = join( ' ', @args[0..@args-2]);
  524. $result = HUEBridge_Call($hash, undef, 'rules', $obj, 'POST');
  525. }
  526. return $result->{error}{description} if( $result->{error} );
  527. return "created rule id $result->{success}{id}" if( $result->{success} && $result->{success}{id} );
  528. return undef;
  529. } elsif($cmd eq 'deleterule') {
  530. return "usage: deleterule <id>" if( @args != 1 );
  531. return "$arg is not a hue rule number" if( $arg !~ m/^\d+$/ );
  532. my $result = HUEBridge_Call($hash, undef, "rules/$arg", undef, 'DELETE');
  533. return $result->{error}{description} if( $result->{error} );
  534. return undef;
  535. } elsif($cmd eq 'createsensor') {
  536. return "usage: createsensor <name> <type> <uniqueid> <swversion> <modelid>" if( @args < 5 );
  537. return "usage: type must be one of: Switch OpenClose Presence Temperature Humidity GenericFlag GenericStatus " if( $args[@args-4] !~ m/Switch|OpenClose|Presence|Temperature|Humidity|Lightlevel|GenericFlag|GenericStatus/ );
  538. my $obj = { 'name' => join( ' ', @args[0..@args-5]),
  539. 'type' => "CLIP$args[@args-4]",
  540. 'uniqueid' => $args[@args-3],
  541. 'swversion' => $args[@args-2],
  542. 'modelid' => $args[@args-1],
  543. 'manufacturername' => 'FHEM-HUE',
  544. };
  545. my $result = HUEBridge_Call($hash, undef, 'sensors', $obj, 'POST');
  546. return $result->{error}{description} if( $result->{error} );
  547. return "created sensor id $result->{success}{id}" if( $result->{success} );
  548. # if( $result->{success} ) {
  549. # my $code = $name ."-S". $result->{success}{id};
  550. # my $devname = "HUEDevice" . $id;
  551. # $devname = $name ."_". $devname if( $hash->{helper}{count} );
  552. # my $define = "$devname HUEDevice sensor $id IODev=$name";
  553. #
  554. # Log3 $name, 4, "$name: create new device '$devname' for address '$id'";
  555. #
  556. # my $cmdret= CommandDefine(undef,$define);
  557. #
  558. # return "created $modules{HUEDevice}{defptr}{$code}->{NAME}" if( defined($modules{HUEDevice}{defptr}{$code}) );
  559. # }
  560. return undef;
  561. } elsif($cmd eq 'deletesensor') {
  562. return "usage: deletesensor <id>" if( @args != 1 );
  563. if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) {
  564. return "$arg is not a hue sensor" if( $defs{$arg}{ID} !~ m/^S/ );
  565. $defs{$arg}{ID} =~ m/S(.*)/;
  566. $arg = $1;
  567. }
  568. my $code = $name ."-S". $arg;
  569. if( defined($modules{HUEDevice}{defptr}{$code}) ) {
  570. CommandDelete( undef, "$modules{HUEDevice}{defptr}{$code}{NAME}" );
  571. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  572. }
  573. return "$arg is not a hue sensor number" if( $arg !~ m/^\d+$/ );
  574. my $result = HUEBridge_Call($hash, undef, "sensors/$arg", undef, 'DELETE');
  575. return $result->{error}{description} if( $result->{error} );
  576. return undef;
  577. } elsif($cmd eq 'configsensor' || $cmd eq 'setsensor' || $cmd eq 'updatesensor') {
  578. return "usage: $cmd <id> <json>" if( @args < 2 );
  579. if( defined $defs{$arg} && $defs{$arg}{TYPE} eq 'HUEDevice' ) {
  580. return "$arg is not a hue sensor" if( $defs{$arg}{ID} !~ m/^S/ );
  581. $defs{$arg}{ID} =~ m/S(.*)/;
  582. $arg = $1;
  583. }
  584. return "$arg is not a hue sensor number" if( $arg !~ m/^\d+$/ );
  585. my $json = join( ' ', @args[1..@args-1]);
  586. my $decoded = eval { decode_json($json) };
  587. if( $@ ) {
  588. Log3 $name, 2, "$name: json error: $@ in $json";
  589. return undef;
  590. }
  591. $json = $decoded;
  592. my $endpoint = '';
  593. $endpoint = 'state' if( $cmd eq 'setsensor' );
  594. $endpoint = 'config' if( $cmd eq 'configsensor' );
  595. my $result = HUEBridge_Call($hash, undef, "sensors/$arg/$endpoint", $json, 'PUT');
  596. return $result->{error}{description} if( $result->{error} );
  597. my $code = $name ."-S". $arg;
  598. if( my $chash = $modules{HUEDevice}{defptr}{$code} ) {
  599. HUEDevice_GetUpdate($chash);
  600. }
  601. return undef;
  602. } elsif($cmd eq 'deletewhitelist') {
  603. return "usage: deletewhitelist <key>" if( @args != 1 );
  604. my $result = HUEBridge_Call($hash, undef, "config/whitelist/$arg", undef, 'DELETE');
  605. return $result->{error}{description} if( $result->{error} );
  606. return undef;
  607. } elsif($cmd eq 'touchlink') {
  608. return "usage: touchlink" if( @args != 0 );
  609. my $obj = { 'touchlink' => JSON::true };
  610. my $result = HUEBridge_Call($hash, undef, 'config', $obj, 'PUT');
  611. return $result->{error}{description} if( $result->{error} );
  612. return undef if( $result->{success} );
  613. return undef;
  614. } elsif($cmd eq 'checkforupdate') {
  615. return "usage: checkforupdate" if( @args != 0 );
  616. my $obj = { swupdate => {'checkforupdate' => JSON::true } };
  617. my $result = HUEBridge_Call($hash, undef, 'config', $obj, 'PUT');
  618. return $result->{error}{description} if( $result->{error} );
  619. return undef if( $result->{success} );
  620. return undef;
  621. } elsif($cmd eq 'active') {
  622. return "can't activate disabled bridge." if(AttrVal($name, "disable", undef));
  623. readingsSingleUpdate($hash, 'state', 'active', 1 );
  624. HUEBridge_OpenDev($hash);
  625. return undef;
  626. } elsif($cmd eq 'inactive') {
  627. readingsSingleUpdate($hash, 'state', 'inactive', 1 );
  628. return undef;
  629. } else {
  630. my $list = "active inactive delete creategroup deletegroup savescene deletescene modifyscene scene createrule updaterule deleterule createsensor deletesensor configsensor setsensor updatesensor deletewhitelist touchlink:noArg checkforupdate:noArg autodetect:noArg autocreate:noArg statusRequest:noArg";
  631. $list .= " swupdate:noArg" if( defined($hash->{updatestate}) && $hash->{updatestate} =~ '^2' );
  632. return "Unknown argument $cmd, choose one of $list";
  633. }
  634. }
  635. sub
  636. HUEBridge_Get($@)
  637. {
  638. my ($hash, $name, $cmd, @args) = @_;
  639. my ($arg, @params) = @args;
  640. return "$name: not paired" if( ReadingsVal($name, 'state', '' ) =~ m/^link/ );
  641. #return "$name: not connected" if( $hash->{STATE} ne 'connected' );
  642. return "$name: get needs at least one parameter" if( !defined($cmd) );
  643. # usage check
  644. if($cmd eq 'devices'
  645. || $cmd eq 'lights') {
  646. my $result = HUEBridge_Call($hash, undef, 'lights', undef);
  647. return $result->{error}{description} if( $result->{error} );
  648. my $ret = "";
  649. foreach my $key ( sort {$a<=>$b} keys %{$result} ) {
  650. my $code = $name ."-". $key;
  651. my $fhem_name ="";
  652. $fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) );
  653. $ret .= sprintf( "%2i: %-25s %-15s %s\n", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type} );
  654. }
  655. $ret = sprintf( "%2s %-25s %-15s %s\n", "ID", "NAME", "FHEM", "TYPE" ) .$ret if( $ret );
  656. return $ret;
  657. } elsif($cmd eq 'groups') {
  658. my $result = HUEBridge_Call($hash, undef, 'groups', undef);
  659. return $result->{error}{description} if( $result->{error} );
  660. $result->{0} = { name => 'Lightset 0', type => 'LightGroup', lights => ["ALL"] };
  661. my $ret = "";
  662. foreach my $key ( sort {$a<=>$b} keys %{$result} ) {
  663. my $code = $name ."-G". $key;
  664. my $fhem_name ="";
  665. $fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) );
  666. $result->{$key}{type} = '' if( !defined($result->{$key}{type}) ); #deCONZ fix
  667. $result->{$key}{class} = '' if( !defined($result->{$key}{class}) ); #deCONZ fix
  668. $result->{$key}{lights} = [] if( !defined($result->{$key}{lights}) ); #deCONZ fix
  669. $ret .= sprintf( "%2i: %-15s %-15s %-15s %-15s %s\n", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type}, $result->{$key}{class}, join( ",", @{$result->{$key}{lights}} ) );
  670. }
  671. $ret = sprintf( "%2s %-15s %-15s %-15s %-15s %s\n", "ID", "NAME", "FHEM", "TYPE", "CLASS", "LIGHTS" ) .$ret if( $ret );
  672. return $ret;
  673. } elsif($cmd eq 'scenes') {
  674. my $result = HUEBridge_Call($hash, undef, 'scenes', undef);
  675. return $result->{error}{description} if( $result->{error} );
  676. my $ret = "";
  677. foreach my $key ( sort {$a cmp $b} keys %{$result} ) {
  678. $ret .= sprintf( "%-20s %-20s", $key, $result->{$key}{name} );
  679. $ret .= sprintf( "%i %i %i %-40s %-20s", $result->{$key}{recycle}, $result->{$key}{locked},$result->{$key}{version}, $result->{$key}{owner}, $result->{$key}{lastupdated}?$result->{$key}{lastupdated}:'' ) if( $arg && $arg eq 'detail' );
  680. $ret .= sprintf( " %s\n", join( ",", @{$result->{$key}{lights}} ) );
  681. }
  682. if( $ret ) {
  683. my $header = sprintf( "%-20s %-20s", "ID", "NAME" );
  684. $header .= sprintf( "%s %s %s %-40s %-20s", "R", "L", "V", "OWNER", "LAST UPDATE" ) if( $arg && $arg eq 'detail' );
  685. $header .= sprintf( " %s\n", "LIGHTS" );
  686. $ret = $header . $ret;
  687. }
  688. return $ret;
  689. } elsif($cmd eq 'rule') {
  690. return "usage: rule <id>" if( @args != 1 );
  691. return "$arg is not a hue rule number" if( $arg !~ m/^\d+$/ );
  692. my $result = HUEBridge_Call($hash, undef, "rules/$arg", undef);
  693. return $result->{error}{description} if( $result->{error} );
  694. my $ret = encode_json($result->{conditions}) ."\n". encode_json($result->{actions});
  695. return $ret;
  696. } elsif($cmd eq 'rules') {
  697. my $result = HUEBridge_Call($hash, undef, 'rules', undef);
  698. return $result->{error}{description} if( $result->{error} );
  699. my $ret = "";
  700. foreach my $key ( sort {$a<=>$b} keys %{$result} ) {
  701. $ret .= sprintf( "%2i: %-20s", $key, $result->{$key}{name} );
  702. $ret .= sprintf( " %s", encode_json($result->{$key}{conditions}) ) if( $arg && $arg eq 'detail' );
  703. $ret .= sprintf( "\n%-24s %s", "", encode_json($result->{$key}{actions}) ) if( $arg && $arg eq 'detail' );
  704. $ret .= "\n";
  705. }
  706. if( $arg && $arg eq 'detail' ) {
  707. $ret = sprintf( "%2s %-20s %s\n", "ID", "NAME", "CONDITIONS/ACTIONS" ) .$ret if( $ret );
  708. } else {
  709. $ret = sprintf( "%2s %-20s\n", "ID", "NAME" ) .$ret if( $ret );
  710. }
  711. return $ret;
  712. } elsif($cmd eq 'sensors') {
  713. my $result = HUEBridge_Call($hash, undef, 'sensors', undef);
  714. return $result->{error}{description} if( $result->{error} );
  715. my $ret = "";
  716. foreach my $key ( sort {$a<=>$b} keys %{$result} ) {
  717. my $code = $name ."-S". $key;
  718. my $fhem_name ="";
  719. $fhem_name = $modules{HUEDevice}{defptr}{$code}->{NAME} if( defined($modules{HUEDevice}{defptr}{$code}) );
  720. $ret .= sprintf( "%2i: %-15s %-15s %-20s", $key, $result->{$key}{name}, $fhem_name, $result->{$key}{type} );
  721. $ret .= sprintf( " %s", encode_json($result->{$key}{state}) ) if( $arg && $arg eq 'detail' );
  722. $ret .= sprintf( "\n%-56s %s", '', encode_json($result->{$key}{config}) ) if( $arg && $arg eq 'detail' );
  723. $ret .= "\n";
  724. }
  725. if( $arg && $arg eq 'detail' ) {
  726. $ret = sprintf( "%2s %-15s %-15s %-20s %s\n", "ID", "NAME", "FHEM", "TYPE", "STATE,CONFIG" ) .$ret if( $ret );
  727. } else {
  728. $ret = sprintf( "%2s %-15s %-15s %-20s\n", "ID", "NAME", "FHEM", "TYPE" ) .$ret if( $ret );
  729. }
  730. return $ret;
  731. } elsif($cmd eq 'whitelist') {
  732. my $result = HUEBridge_Call($hash, undef, 'config', undef);
  733. return $result->{error}{description} if( $result->{error} );
  734. my $ret = "";
  735. my $whitelist = $result->{whitelist};
  736. foreach my $key ( sort {$whitelist->{$a}{'last use date'} cmp $whitelist->{$b}{'last use date'}} keys %{$whitelist} ) {
  737. $ret .= sprintf( "%-20s %-20s %-30s %s\n", $whitelist->{$key}{'create date'}, , $whitelist->{$key}{'last use date'}, $whitelist->{$key}{name}, $key );
  738. }
  739. $ret = sprintf( "%-20s %-20s %-30s %s\n", "CREATE", "LAST USE", "NAME", "KEY" ) .$ret if( $ret );
  740. return $ret;
  741. } else {
  742. return "Unknown argument $cmd, choose one of lights:noArg groups:noArg scenes:noArg rule rules:noArg sensors:noArg whitelist:noArg";
  743. }
  744. }
  745. sub
  746. HUEBridge_GetUpdate($)
  747. {
  748. my ($hash) = @_;
  749. my $name = $hash->{NAME};
  750. if(!$hash->{LOCAL}) {
  751. RemoveInternalTimer($hash);
  752. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "HUEBridge_GetUpdate", $hash, 0);
  753. }
  754. if( $hash->{websocketport} && !$hash->{PORT} ) {
  755. HUEBridge_openWebsocket($hash);
  756. }
  757. my $type;
  758. my $result;
  759. my $poll_devices = AttrVal($name, "pollDevices", 1);
  760. if( $poll_devices ) {
  761. my ($now) = gettimeofday();
  762. if( $poll_devices > 1 || $hash->{LOCAL} || $now - $hash->{helper}{last_config_timestamp} > 300 ) {
  763. $result = HUEBridge_Call($hash, $hash, undef, undef);
  764. $hash->{helper}{last_config_timestamp} = $now;
  765. } else {
  766. $type = 'lights';
  767. $result = HUEBridge_Call($hash, $hash, 'lights', undef);
  768. }
  769. } else {
  770. $type = 'config';
  771. $result = HUEBridge_Call($hash, $hash, 'config', undef);
  772. }
  773. return undef if( !defined($result) );
  774. HUEBridge_dispatch( {hash=>$hash,chash=>$hash,type=>$type}, undef, undef, $result );
  775. #HUEBridge_Parse($hash, $result);
  776. return undef;
  777. }
  778. my %dim_values = (
  779. 0 => "dim06%",
  780. 1 => "dim12%",
  781. 2 => "dim18%",
  782. 3 => "dim25%",
  783. 4 => "dim31%",
  784. 5 => "dim37%",
  785. 6 => "dim43%",
  786. 7 => "dim50%",
  787. 8 => "dim56%",
  788. 9 => "dim62%",
  789. 10 => "dim68%",
  790. 11 => "dim75%",
  791. 12 => "dim81%",
  792. 13 => "dim87%",
  793. 14 => "dim93%",
  794. );
  795. sub
  796. HUEBridge_updateGroups($$)
  797. {
  798. my($hash,$lights) = @_;
  799. my $name = $hash->{NAME};
  800. my $createGroupReadings = AttrVal($hash->{NAME},"createGroupReadings",undef);
  801. return if( !defined($createGroupReadings) );
  802. $createGroupReadings = ($createGroupReadings eq "1");
  803. my $groups = {};
  804. foreach my $light ( split(',', $lights) ) {
  805. foreach my $chash ( values %{$modules{HUEDevice}{defptr}} ) {
  806. next if( !$chash->{IODev} );
  807. next if( !$chash->{lights} );
  808. next if( $chash->{IODev}{NAME} ne $name );
  809. next if( $chash->{helper}{devtype} ne 'G' );
  810. next if( ",$chash->{lights}," !~ m/,$light,/ );
  811. next if( $createGroupReadings && !AttrVal($chash->{NAME},"createGroupReadings", 1) );
  812. next if( !$createGroupReadings && !AttrVal($chash->{NAME},"createGroupReadings", undef) );
  813. $groups->{$chash->{ID}} = $chash;
  814. }
  815. }
  816. foreach my $chash ( values %{$groups} ) {
  817. my $count = 0;
  818. my %readings;
  819. foreach my $light ( split(',', $chash->{lights}) ) {
  820. next if( !$light );
  821. my $current = $modules{HUEDevice}{defptr}{"$name-$light"}{helper};
  822. $readings{ct} += $current->{ct};
  823. $readings{bri} += $current->{bri};
  824. $readings{pct} += $current->{pct};
  825. $readings{sat} += $current->{sat};
  826. $readings{on} |= ($current->{on}?'1':'0');
  827. $readings{reachable} |= ($current->{reachable}?'1':'0');
  828. if( !defined($readings{alert}) ) {
  829. $readings{alert} = $current->{alert};
  830. } elsif( $readings{alert} ne $current->{alert} ) {
  831. $readings{alert} = "nonuniform";
  832. }
  833. if( !defined($readings{colormode}) ) {
  834. $readings{colormode} = $current->{colormode};
  835. } elsif( $readings{colormode} ne $current->{colormode} ) {
  836. $readings{colormode} = "nonuniform";
  837. }
  838. if( !defined($readings{effect}) ) {
  839. $readings{effect} = $current->{effect};
  840. } elsif( $readings{effect} ne $current->{effect} ) {
  841. $readings{effect} = "nonuniform";
  842. }
  843. ++$count;
  844. }
  845. $readings{ct} = int($readings{ct} / $count + 0.5);
  846. $readings{bri} = int($readings{bri} / $count + 0.5);
  847. $readings{pct} = int($readings{pct} / $count + 0.5);
  848. $readings{sat} = int($readings{sat} / $count + 0.5);
  849. if( $readings{on} ) {
  850. if( $readings{pct} > 0
  851. && $readings{pct} < 100 ) {
  852. $readings{state} = $dim_values{int($readings{pct}/7)};
  853. }
  854. $readings{state} = 'off' if( $readings{pct} == 0 );
  855. $readings{state} = 'on' if( $readings{pct} == 100 );
  856. } else {
  857. $readings{pct} = 0;
  858. $readings{state} = 'off';
  859. }
  860. $readings{onoff} = $readings{on};
  861. delete $readings{on};
  862. readingsBeginUpdate($chash);
  863. foreach my $key ( keys %readings ) {
  864. if( defined($readings{$key}) ) {
  865. readingsBulkUpdate($chash, $key, $readings{$key}, 1) if( !defined($chash->{helper}{$key}) || $chash->{helper}{$key} ne $readings{$key} );
  866. $chash->{helper}{$key} = $readings{$key};
  867. }
  868. }
  869. readingsEndUpdate($chash,1);
  870. }
  871. }
  872. sub
  873. HUEBridge_Parse($$)
  874. {
  875. my($hash,$config) = @_;
  876. my $name = $hash->{NAME};
  877. Log3 $name, 4, "parse status message for $name";
  878. #Log3 $name, 5, Dumper $config;
  879. #Log 3, Dumper $config;
  880. $config = $config->{config} if( defined($config->{config}) );
  881. HUEBridge_fillBridgeInfo($hash, $config);
  882. $hash->{zigbeechannel} = $config->{zigbeechannel};
  883. if( my $utc = $config->{UTC} ) {
  884. substr( $utc, 10, 1, '_' );
  885. if( my $localtime = $config->{localtime} ) {
  886. $localtime = TimeNow() if( $localtime eq 'none' );
  887. substr( $localtime, 10, 1, '_' );
  888. $hash->{helper}{offsetUTC} = SVG_time_to_sec($localtime) - SVG_time_to_sec($utc);
  889. } else {
  890. Log3 $name, 2, "$name: missing localtime configuration";
  891. }
  892. }
  893. if( defined( $config->{swupdate} ) ) {
  894. my $txt = $config->{swupdate}->{text};
  895. readingsSingleUpdate($hash, "swupdate", $txt, 1) if( $txt && $txt ne ReadingsVal($name,"swupdate","") );
  896. if( defined($hash->{updatestate}) ){
  897. readingsSingleUpdate($hash, 'state', 'update done', 1 ) if( $config->{swupdate}->{updatestate} == 0 && $hash->{helper}{updatestate} >= 2 );
  898. readingsSingleUpdate($hash, 'state', 'update failed', 1 ) if( $config->{swupdate}->{updatestate} == 2 && $hash->{helper}{updatestate} == 3 );
  899. }
  900. $hash->{updatestate} = $config->{swupdate}->{updatestate};
  901. $hash->{helper}{updatestate} = $hash->{updatestate};
  902. if( $config->{swupdate}->{devicetypes} ) {
  903. my $devicetypes;
  904. $devicetypes .= 'bridge' if( $config->{swupdate}->{devicetypes}->{bridge} );
  905. $devicetypes .= ',' if( $devicetypes && scalar(@{$config->{swupdate}->{devicetypes}->{lights}}) );
  906. $devicetypes .= join( ",", @{$config->{swupdate}->{devicetypes}->{lights}} ) if( $config->{swupdate}->{devicetypes}->{lights} );
  907. $hash->{updatestate} .= " [$devicetypes]" if( $devicetypes );
  908. }
  909. } elsif ( defined( $hash->{swupdate} ) ) {
  910. delete( $hash->{updatestate} );
  911. delete( $hash->{helper}{updatestate} );
  912. }
  913. readingsSingleUpdate($hash, 'state', $hash->{READINGS}{state}{VAL}, 0);
  914. }
  915. sub
  916. HUEBridge_Autocreate($;$)
  917. {
  918. my ($hash,$force)= @_;
  919. my $name = $hash->{NAME};
  920. if( !$force ) {
  921. foreach my $d (keys %defs) {
  922. next if($defs{$d}{TYPE} ne "autocreate");
  923. return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
  924. }
  925. }
  926. my $autocreated = 0;
  927. my $result = HUEBridge_Call($hash,undef, 'lights', undef);
  928. foreach my $key ( keys %{$result} ) {
  929. my $id= $key;
  930. my $code = $name ."-". $id;
  931. if( defined($modules{HUEDevice}{defptr}{$code}) ) {
  932. Log3 $name, 5, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'";
  933. next;
  934. }
  935. my $devname = "HUEDevice" . $id;
  936. $devname = $name ."_". $devname if( $hash->{helper}{count} );
  937. my $define= "$devname HUEDevice $id IODev=$name";
  938. Log3 $name, 4, "$name: create new device '$devname' for address '$id'";
  939. my $cmdret= CommandDefine(undef,$define);
  940. if($cmdret) {
  941. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  942. } else {
  943. $cmdret= CommandAttr(undef,"$devname alias ".$result->{$id}{name});
  944. $cmdret= CommandAttr(undef,"$devname room HUEDevice");
  945. $cmdret= CommandAttr(undef,"$devname IODev $name");
  946. HUEDeviceSetIcon($devname);
  947. $defs{$devname}{helper}{fromAutocreate} = 1 ;
  948. $autocreated++;
  949. }
  950. }
  951. $result = HUEBridge_Call($hash,undef, 'groups', undef);
  952. $result->{0} = { name => "Lightset 0", };
  953. foreach my $key ( keys %{$result} ) {
  954. my $id= $key;
  955. my $code = $name ."-G". $id;
  956. if( defined($modules{HUEDevice}{defptr}{$code}) ) {
  957. Log3 $name, 5, "$name: id '$id' already defined as '$modules{HUEDevice}{defptr}{$code}->{NAME}'";
  958. next;
  959. }
  960. my $devname= "HUEGroup" . $id;
  961. $devname = $name ."_". $devname if( $hash->{helper}{count} );
  962. my $define= "$devname HUEDevice group $id IODev=$name";
  963. Log3 $name, 4, "$name: create new group '$devname' for address '$id'";
  964. my $cmdret= CommandDefine(undef,$define);
  965. if($cmdret) {
  966. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  967. } else {
  968. $cmdret= CommandAttr(undef,"$devname alias ".$result->{$id}{name});
  969. $cmdret= CommandAttr(undef,"$devname room HUEDevice");
  970. $cmdret= CommandAttr(undef,"$devname group HUEGroup");
  971. $cmdret= CommandAttr(undef,"$devname IODev $name");
  972. HUEDeviceSetIcon($devname);
  973. $defs{$devname}{helper}{fromAutocreate} = 1 ;
  974. $autocreated++;
  975. }
  976. }
  977. if( $autocreated ) {
  978. Log3 $name, 2, "$name: autocreated $autocreated devices";
  979. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  980. }
  981. return "created $autocreated devices";
  982. }
  983. sub
  984. HUEBridge_ProcessResponse($$)
  985. {
  986. my ($hash,$obj) = @_;
  987. my $name = $hash->{NAME};
  988. #Log3 $name, 3, ref($obj);
  989. #Log3 $name, 3, "Receiving: " . Dumper $obj;
  990. if( ref($obj) eq 'ARRAY' ) {
  991. if( defined($obj->[0]->{error})) {
  992. my $error = $obj->[0]->{error}->{'description'};
  993. readingsSingleUpdate($hash, 'lastError', $error, 1 );
  994. }
  995. if( !AttrVal( $name,'queryAfterSet', 1 ) ) {
  996. my $successes;
  997. my $errors;
  998. my %json = ();
  999. foreach my $item (@{$obj}) {
  1000. if( my $success = $item->{success} ) {
  1001. next if( ref($success) ne 'HASH' );
  1002. foreach my $key ( keys %{$success} ) {
  1003. my @l = split( '/', $key );
  1004. next if( !$l[1] );
  1005. if( $l[1] eq 'lights' && $l[3] eq 'state' ) {
  1006. $json{$l[2]}->{state}->{$l[4]} = $success->{$key};
  1007. $successes++;
  1008. } elsif( $l[1] eq 'groups' && $l[3] eq 'action' ) {
  1009. my $code = $name ."-G". $l[2];
  1010. my $d = $modules{HUEDevice}{defptr}{$code};
  1011. if( my $lights = $d->{lights} ) {
  1012. foreach my $light ( split(',', $lights) ) {
  1013. $json{$light}->{state}->{$l[4]} = $success->{$key};
  1014. $successes++;
  1015. }
  1016. }
  1017. }
  1018. }
  1019. } elsif( my $error = $item->{error} ) {
  1020. my $msg = $error->{'description'};
  1021. Log3 $name, 3, $msg;
  1022. $errors++;
  1023. }
  1024. }
  1025. my $changed = "";
  1026. foreach my $id ( keys %json ) {
  1027. my $code = $name ."-". $id;
  1028. if( my $chash = $modules{HUEDevice}{defptr}{$code} ) {
  1029. #$json{$id}->{state}->{reachable} = 1;
  1030. if( HUEDevice_Parse( $chash, $json{$id} ) ) {
  1031. $changed .= "," if( $changed );
  1032. $changed .= $chash->{ID};
  1033. }
  1034. }
  1035. }
  1036. HUEBridge_updateGroups($hash, $changed) if( $changed );
  1037. }
  1038. #return undef if( !$errors && $successes );
  1039. return ($obj->[0]);
  1040. } elsif( ref($obj) eq 'HASH' ) {
  1041. return $obj;
  1042. }
  1043. return undef;
  1044. }
  1045. sub HUEBridge_Register($)
  1046. {
  1047. my ($hash) = @_;
  1048. my $obj = {
  1049. 'devicetype' => 'fhem',
  1050. };
  1051. if( !$hash->{helper}{apiversion} || $hash->{helper}{apiversion} < (1<<16) + (12<<8) ) {
  1052. $obj->{username} = AttrVal($hash->{NAME}, 'key', '');
  1053. }
  1054. return HUEBridge_Call($hash, undef, undef, $obj);
  1055. }
  1056. #Executes a JSON RPC
  1057. sub
  1058. HUEBridge_Call($$$$;$)
  1059. {
  1060. my ($hash,$chash,$path,$obj,$method) = @_;
  1061. my $name = $hash->{NAME};
  1062. if( IsDisabled($name) ) {
  1063. readingsSingleUpdate($hash, 'state', 'inactive', 1 ) if( ReadingsVal($name,'state','' ) ne 'inactive' );
  1064. return undef;
  1065. }
  1066. #Log3 $hash->{NAME}, 5, "Sending: " . Dumper $obj;
  1067. my $json = undef;
  1068. $json = encode_json($obj) if $obj;
  1069. # @TODO: repeat twice?
  1070. for( my $attempt=0; $attempt<2; $attempt++ ) {
  1071. my $blocking;
  1072. my $res = undef;
  1073. if( !defined($attr{$name}{httpUtils}) ) {
  1074. $blocking = 1;
  1075. $res = HUEBridge_HTTP_Call($hash,$path,$json,$method);
  1076. } else {
  1077. $blocking = $attr{$name}{httpUtils} < 1;
  1078. $res = HUEBridge_HTTP_Call2($hash,$chash,$path,$json,$method);
  1079. }
  1080. return $res if( !$blocking || defined($res) );
  1081. Log3 $name, 3, "HUEBridge_Call: failed, retrying";
  1082. HUEBridge_Detect($hash) if( defined($hash->{NUPNP}) );
  1083. }
  1084. Log3 $name, 3, "HUEBridge_Call: failed";
  1085. return undef;
  1086. }
  1087. #JSON RPC over HTTP
  1088. sub
  1089. HUEBridge_HTTP_Call($$$;$)
  1090. {
  1091. my ($hash,$path,$obj,$method) = @_;
  1092. my $name = $hash->{NAME};
  1093. #return { state => {reachable => 0 } } if($attr{$name} && $attr{$name}{disable});
  1094. my $uri = "http://" . $hash->{host} . "/api";
  1095. if( defined($obj) ) {
  1096. $method = 'PUT' if( !$method );
  1097. if( ReadingsVal($name, 'state', '') eq 'pairing' ) {
  1098. $method = 'POST';
  1099. } else {
  1100. $uri .= "/" . AttrVal($name, "key", "");
  1101. }
  1102. } else {
  1103. $uri .= "/" . AttrVal($name, "key", "");
  1104. }
  1105. $method = 'GET' if( !$method );
  1106. if( defined $path) {
  1107. $uri .= "/" . $path;
  1108. }
  1109. #Log3 $name, 3, "Url: " . $uri;
  1110. Log3 $name, 4, "using HUEBridge_HTTP_Request: $method ". ($path?$path:'');
  1111. my $ret = HUEBridge_HTTP_Request(0,$uri,$method,undef,$obj,AttrVal($name,'noshutdown', 1));
  1112. #Log3 $name, 3, Dumper $ret;
  1113. if( !defined($ret) ) {
  1114. return undef;
  1115. } elsif($ret eq '') {
  1116. return undef;
  1117. } elsif($ret =~ /^error:(\d){3}$/) {
  1118. my %result = { error => "HTTP Error Code $1" };
  1119. return \%result;
  1120. }
  1121. if( !$ret ) {
  1122. Log3 $name, 2, "$name: empty answer received for $uri";
  1123. return undef;
  1124. } elsif( $ret =~ m'HTTP/1.1 200 OK' ) {
  1125. Log3 $name, 4, "$name: empty answer received for $uri";
  1126. return undef;
  1127. } elsif( $ret !~ m/^[\[{].*[\]}]$/ ) {
  1128. Log3 $name, 2, "$name: invalid json detected for $uri: $ret";
  1129. return undef;
  1130. }
  1131. my $decoded = eval { decode_json($ret) };
  1132. Log3 $name, 2, "$name: json error: $@ in $ret" if( $@ );
  1133. return HUEBridge_ProcessResponse($hash, $decoded);
  1134. }
  1135. sub
  1136. HUEBridge_HTTP_Call2($$$$;$)
  1137. {
  1138. my ($hash,$chash,$path,$obj,$method) = @_;
  1139. my $name = $hash->{NAME};
  1140. #return { state => {reachable => 0 } } if($attr{$name} && $attr{$name}{disable});
  1141. my $url = "http://" . $hash->{host} . "/api";
  1142. my $blocking = $attr{$name}{httpUtils} < 1;
  1143. $blocking = 1 if( !defined($chash) );
  1144. if( defined($obj) ) {
  1145. $method = 'PUT' if( !$method );
  1146. if( ReadingsVal($name, 'state', '') eq 'pairing' ) {
  1147. $method = 'POST';
  1148. $blocking = 1;
  1149. } else {
  1150. $url .= "/" . AttrVal($name, "key", "");
  1151. }
  1152. } else {
  1153. $url .= "/" . AttrVal($name, "key", "");
  1154. }
  1155. $method = 'GET' if( !$method );
  1156. if( defined $path) {
  1157. $url .= "/" . $path;
  1158. }
  1159. #Log3 $name, 3, "Url: " . $url;
  1160. #Log 2, $path;
  1161. if( $blocking ) {
  1162. Log3 $name, 4, "using HttpUtils_BlockingGet: $method ". ($path?$path:'');
  1163. my($err,$data) = HttpUtils_BlockingGet({
  1164. url => $url,
  1165. timeout => 4,
  1166. method => $method,
  1167. noshutdown => AttrVal($name,'noshutdown', 1),
  1168. header => "Content-Type: application/json",
  1169. data => $obj,
  1170. });
  1171. if( !$data ) {
  1172. Log3 $name, 2, "$name: empty answer received for $url";
  1173. return undef;
  1174. } elsif( $data =~ m'HTTP/1.1 200 OK' ) {
  1175. Log3 $name, 4, "$name: empty answer received for $url";
  1176. return undef;
  1177. } elsif( $data !~ m/^[\[{].*[\]}]$/ ) {
  1178. Log3 $name, 2, "$name: invalid json detected for $url: $data";
  1179. return undef;
  1180. }
  1181. my $json = eval { decode_json($data) };
  1182. Log3 $name, 2, "$name: json error: $@ in $data" if( $@ );
  1183. return undef if( !$json );
  1184. return HUEBridge_ProcessResponse($hash, $json);
  1185. HUEBridge_dispatch( {hash=>$hash,chash=>$chash,type=>$path},$err,$data );
  1186. } else {
  1187. Log3 $name, 4, "using HttpUtils_NonblockingGet: $method ". ($path?$path:'');
  1188. my($err,$data) = HttpUtils_NonblockingGet({
  1189. url => $url,
  1190. timeout => 10,
  1191. method => $method,
  1192. noshutdown => AttrVal($name,'noshutdown', 1),
  1193. header => "Content-Type: application/json",
  1194. data => $obj,
  1195. hash => $hash,
  1196. chash => $chash,
  1197. type => $path,
  1198. callback => \&HUEBridge_dispatch,
  1199. });
  1200. return undef;
  1201. }
  1202. }
  1203. sub
  1204. HUEBridge_dispatch($$$;$)
  1205. {
  1206. my ($param, $err, $data, $json) = @_;
  1207. my $hash = $param->{hash};
  1208. my $name = $hash->{NAME};
  1209. #Log3 $name, 5, "HUEBridge_dispatch";
  1210. if( $err ) {
  1211. Log3 $name, 2, "$name: http request failed: $err";
  1212. } elsif( $data || $json ) {
  1213. if( !$data && !$json ) {
  1214. Log3 $name, 2, "$name: empty answer received";
  1215. return undef;
  1216. } elsif( $data && $data !~ m/^[\[{].*[\]}]$/ ) {
  1217. Log3 $name, 2, "$name: invalid json detected: $data";
  1218. return undef;
  1219. }
  1220. my $queryAfterSet = AttrVal( $name,'queryAfterSet', 1 );
  1221. if( !$json ) {
  1222. $json = eval { decode_json($data) } if( !$json );
  1223. Log3 $name, 2, "$name: json error: $@ in $data" if( $@ );
  1224. }
  1225. return undef if( !$json );
  1226. my $type = $param->{type};
  1227. if( ref($json) eq 'ARRAY' ) {
  1228. HUEBridge_ProcessResponse($hash,$json) if( !$queryAfterSet );
  1229. if( defined($json->[0]->{error}))
  1230. {
  1231. my $error = $json->[0]->{error}->{'description'};
  1232. readingsSingleUpdate($hash, 'lastError', $error, 1 );
  1233. Log3 $name, 3, $error;
  1234. }
  1235. #return ($json->[0]);
  1236. }
  1237. if( $hash == $param->{chash} ) {
  1238. if( !defined($type) ) {
  1239. HUEBridge_Parse($hash,$json->{config});
  1240. if( defined($json->{sensors}) ) {
  1241. my $sensors = $json->{sensors};
  1242. foreach my $id ( keys %{$sensors} ) {
  1243. my $code = $name ."-S". $id;
  1244. my $chash = $modules{HUEDevice}{defptr}{$code};
  1245. if( defined($chash) ) {
  1246. HUEDevice_Parse($chash,$sensors->{$id});
  1247. } else {
  1248. Log3 $name, 4, "$name: message for unknow sensor received: $code";
  1249. }
  1250. }
  1251. }
  1252. if( defined($json->{groups}) ) {
  1253. my $groups = $json->{groups};
  1254. foreach my $id ( keys %{$groups} ) {
  1255. my $code = $name ."-G". $id;
  1256. my $chash = $modules{HUEDevice}{defptr}{$code};
  1257. if( defined($chash) ) {
  1258. HUEDevice_Parse($chash,$groups->{$id});
  1259. } else {
  1260. Log3 $name, 2, "$name: message for unknow group received: $code";
  1261. }
  1262. }
  1263. }
  1264. $type = 'lights';
  1265. $json = $json->{lights};
  1266. }
  1267. if( $type eq 'lights' ) {
  1268. my $changed = "";
  1269. my $lights = $json;
  1270. foreach my $id ( keys %{$lights} ) {
  1271. my $code = $name ."-". $id;
  1272. my $chash = $modules{HUEDevice}{defptr}{$code};
  1273. if( defined($chash) ) {
  1274. if( HUEDevice_Parse($chash,$lights->{$id}) ) {
  1275. $changed .= "," if( $changed );
  1276. $changed .= $chash->{ID};
  1277. }
  1278. } else {
  1279. Log3 $name, 2, "$name: message for unknow device received: $code";
  1280. }
  1281. }
  1282. HUEBridge_updateGroups($hash, $changed) if( $changed );
  1283. } elsif( $type =~ m/^config$/ ) {
  1284. HUEBridge_Parse($hash,$json);
  1285. } else {
  1286. Log3 $name, 2, "$name: message for unknow type received: $type";
  1287. Log3 $name, 4, Dumper $json;
  1288. }
  1289. } elsif( $type =~ m/^lights\/(\d*)$/ ) {
  1290. if( HUEDevice_Parse($param->{chash},$json) ) {
  1291. HUEBridge_updateGroups($hash, $param->{chash}{ID});
  1292. }
  1293. } elsif( $type =~ m/^groups\/(\d*)$/ ) {
  1294. HUEDevice_Parse($param->{chash},$json);
  1295. } elsif( $type =~ m/^sensors\/(\d*)$/ ) {
  1296. HUEDevice_Parse($param->{chash},$json);
  1297. } elsif( $type =~ m/^lights\/(\d*)\/state$/ ) {
  1298. if( $queryAfterSet ) {
  1299. my $chash = $param->{chash};
  1300. if( $chash->{helper}->{update_timeout} ) {
  1301. RemoveInternalTimer($chash);
  1302. InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0);
  1303. } else {
  1304. RemoveInternalTimer($chash);
  1305. HUEDevice_GetUpdate( $chash );
  1306. }
  1307. }
  1308. } elsif( $type =~ m/^groups\/(\d*)\/action$/ ) {
  1309. my $chash = $param->{chash};
  1310. if( $chash->{helper}->{update_timeout} ) {
  1311. RemoveInternalTimer($chash);
  1312. InternalTimer(gettimeofday()+1, "HUEDevice_GetUpdate", $chash, 0);
  1313. } else {
  1314. RemoveInternalTimer($chash);
  1315. HUEDevice_GetUpdate( $chash );
  1316. }
  1317. } else {
  1318. Log3 $name, 2, "$name: message for unknow type received: $type";
  1319. Log3 $name, 4, Dumper $json;
  1320. }
  1321. }
  1322. }
  1323. #adapted version of the CustomGetFileFromURL subroutine from HttpUtils.pm
  1324. sub
  1325. HUEBridge_HTTP_Request($$$@)
  1326. {
  1327. my ($quiet, $url, $method, $timeout, $data, $noshutdown) = @_;
  1328. $timeout = 4.0 if(!defined($timeout));
  1329. my $displayurl= $quiet ? "<hidden>" : $url;
  1330. if($url !~ /^(http|https):\/\/([^:\/]+)(:\d+)?(\/.*)$/) {
  1331. Log3 undef, 1, "HUEBridge_HTTP_Request $displayurl: malformed or unsupported URL";
  1332. return undef;
  1333. }
  1334. my ($protocol,$host,$port,$path)= ($1,$2,$3,$4);
  1335. if(defined($port)) {
  1336. $port =~ s/^://;
  1337. } else {
  1338. $port = ($protocol eq "https" ? 443: 80);
  1339. }
  1340. $path= '/' unless defined($path);
  1341. my $conn;
  1342. if($protocol eq "https") {
  1343. eval "use IO::Socket::SSL";
  1344. if($@) {
  1345. Log3 undef, 1, $@;
  1346. } else {
  1347. $conn = IO::Socket::SSL->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
  1348. }
  1349. } else {
  1350. $conn = IO::Socket::INET->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
  1351. }
  1352. if(!$conn) {
  1353. Log3 undef, 1, "HUEBridge_HTTP_Request $displayurl: Can't connect to $protocol://$host:$port";
  1354. undef $conn;
  1355. return undef;
  1356. }
  1357. $host =~ s/:.*//;
  1358. #my $hdr = ($data ? "POST" : "GET")." $path HTTP/1.0\r\nHost: $host\r\n";
  1359. my $hdr = $method." $path HTTP/1.0\r\nHost: $host\r\n";
  1360. if(defined($data)) {
  1361. $hdr .= "Content-Length: ".length($data)."\r\n";
  1362. $hdr .= "Content-Type: application/json";
  1363. }
  1364. $hdr .= "\r\n\r\n";
  1365. syswrite $conn, $hdr;
  1366. syswrite $conn, $data if(defined($data));
  1367. shutdown $conn, 1 if(!$noshutdown);
  1368. my ($buf, $ret) = ("", "");
  1369. $conn->timeout($timeout);
  1370. for(;;) {
  1371. my ($rout, $rin) = ('', '');
  1372. vec($rin, $conn->fileno(), 1) = 1;
  1373. my $nfound = select($rout=$rin, undef, undef, $timeout);
  1374. if($nfound <= 0) {
  1375. Log3 undef, 1, "HUEBridge_HTTP_Request $displayurl: Select timeout/error: $!";
  1376. undef $conn;
  1377. return undef;
  1378. }
  1379. my $len = sysread($conn,$buf,65536);
  1380. last if(!defined($len) || $len <= 0);
  1381. $ret .= $buf;
  1382. }
  1383. $ret=~ s/(.*?)\r\n\r\n//s; # Not greedy: switch off the header.
  1384. my @header= split("\r\n", $1);
  1385. my $hostpath= $quiet ? "<hidden>" : $host . $path;
  1386. Log3 undef, 5, "HUEBridge_HTTP_Request $displayurl: Got data, length: ".length($ret);
  1387. if(!length($ret)) {
  1388. Log3 undef, 4, "HUEBridge_HTTP_Request $displayurl: Zero length data, header follows...";
  1389. for (@header) {
  1390. Log3 undef, 4, "HUEBridge_HTTP_Request $displayurl: $_";
  1391. }
  1392. }
  1393. undef $conn;
  1394. if($header[0] =~ /^[^ ]+ ([\d]{3})/ && $1 != 200) {
  1395. my %result = { error => "error: $1" };
  1396. return \%result;
  1397. }
  1398. return $ret;
  1399. }
  1400. sub
  1401. HUEBridge_Attr($$$)
  1402. {
  1403. my ($cmd, $name, $attrName, $attrVal) = @_;
  1404. my $orig = $attrVal;
  1405. $attrVal = int($attrVal) if($attrName eq "interval");
  1406. $attrVal = 60 if($attrName eq "interval" && $attrVal < 60 && $attrVal != 0);
  1407. if( $attrName eq "disable" ) {
  1408. my $hash = $defs{$name};
  1409. if( $cmd eq 'set' && $attrVal ne "0" ) {
  1410. readingsSingleUpdate($hash, 'state', 'disabled', 1 );
  1411. } else {
  1412. $attr{$name}{$attrName} = 0;
  1413. readingsSingleUpdate($hash, 'state', 'active', 1 );
  1414. HUEBridge_OpenDev($hash);
  1415. }
  1416. } elsif( $attrName eq "disabledForIntervals" ) {
  1417. my $hash = $defs{$name};
  1418. if( $cmd eq 'set' ) {
  1419. $attr{$name}{$attrName} = $attrVal;
  1420. } else {
  1421. $attr{$name}{$attrName} = "";
  1422. }
  1423. readingsSingleUpdate($hash, 'state', IsDisabled($name)?'disabled':'active', 1 );
  1424. HUEBridge_OpenDev($hash) if( !IsDisabled($name) );
  1425. }
  1426. if( $cmd eq 'set' ) {
  1427. if( $orig ne $attrVal ) {
  1428. $attr{$name}{$attrName} = $attrVal;
  1429. return $attrName ." set to ". $attrVal;
  1430. }
  1431. }
  1432. return;
  1433. }
  1434. 1;
  1435. =pod
  1436. =item summary module for the phillips hue bridge
  1437. =item summary_DE Modul f&uuml;r die Philips HUE Bridge
  1438. =begin html
  1439. <a name="HUEBridge"></a>
  1440. <h3>HUEBridge</h3>
  1441. <ul>
  1442. Module to access the bridge of the phillips hue lighting system.<br><br>
  1443. The actual hue bulbs, living colors or living whites devices are defined as <a href="#HUEDevice">HUEDevice</a> devices.
  1444. <br><br>
  1445. All newly found devices and groups are autocreated at startup and added to the room HUEDevice.
  1446. <br><br>
  1447. Notes:
  1448. <ul>
  1449. <li>This module needs <code>JSON</code>.<br>
  1450. Please install with '<code>cpan install JSON</code>' or your method of choice.</li>
  1451. </ul>
  1452. <br><br>
  1453. <a name="HUEBridge_Define"></a>
  1454. <b>Define</b>
  1455. <ul>
  1456. <code>define &lt;name&gt; HUEBridge [&lt;host&gt;] [&lt;interval&gt;]</code><br>
  1457. <br>
  1458. Defines a HUEBridge device with address &lt;host&gt;.<br><br>
  1459. If [&lt;host&gt;] is not given the module will try to autodetect the bridge with the hue portal services.<br><br>
  1460. The bridge status will be updated every &lt;interval&gt; seconds. The default and minimum is 60.<br><br>
  1461. After a new bridge is created the pair button on the bridge has to be pressed.<br><br>
  1462. Examples:
  1463. <ul>
  1464. <code>define bridge HUEBridge 10.0.1.1</code><br>
  1465. </ul>
  1466. </ul><br>
  1467. <a name="HUEBridge_Get"></a>
  1468. <b>Get</b>
  1469. <ul>
  1470. <li>lights<br>
  1471. list the lights known to the bridge.</li>
  1472. <li>groups<br>
  1473. list the groups known to the bridge.</li>
  1474. <li>scenes [detail]<br>
  1475. list the scenes known to the bridge.</li>
  1476. <li>rule &lt;id&gt; <br>
  1477. list the rule with &lt;id&gt;.</li>
  1478. <li>rules [detail] <br>
  1479. list the rules known to the bridge.</li>
  1480. <li>sensors [detail] <br>
  1481. list the sensors known to the bridge.</li>
  1482. <li>whitelist<br>
  1483. list the whitlist of the bridge.</li>
  1484. </ul><br>
  1485. <a name="HUEBridge_Set"></a>
  1486. <b>Set</b>
  1487. <ul>
  1488. <li>autocreate<br>
  1489. Create fhem devices for all bridge devices.</li>
  1490. <li>autodetect<br>
  1491. Initiate the detection of new ZigBee devices. After aproximately one minute any newly detected
  1492. devices can be listed with <code>get &lt;bridge&gt; devices</code> and the corresponding fhem devices
  1493. can be created by <code>set &lt;bridge&gt; autocreate</code>.</li>
  1494. <li>delete &lt;name&gt;|&lt;id&gt;<br>
  1495. Deletes the given device in the bridge and deletes the associated fhem device.</li>
  1496. <li>creategroup &lt;name&gt; &lt;lights&gt;<br>
  1497. Create a group out of &lt;lights&gt; in the bridge.
  1498. The lights are given as a comma sparated list of fhem device names or bridge light numbers.</li>
  1499. <li>deletegroup &lt;name&gt;|&lt;id&gt;<br>
  1500. Deletes the given group in the bridge and deletes the associated fhem device.</li>
  1501. <li>savescene &lt;name&gt; &lt;lights&gt;<br>
  1502. Create a scene from the current state of &lt;lights&gt; in the bridge.
  1503. The lights are given as a comma sparated list of fhem device names or bridge light numbers.</li>
  1504. <li>modifyscene &lt;id&gt; &lt;light&gt; &lt;light-args&gt;<br>
  1505. Modifys the given scene in the bridge.</li>
  1506. <li>scene &lt;id&gt;<br>
  1507. Recalls the scene with the given id.</li>
  1508. <li>createrule &lt;name&gt; &lt;conditions&amp;actions json&gt;<br>
  1509. Creates a new rule in the bridge.</li>
  1510. <li>deleterule &lt;id&gt;<br>
  1511. Deletes the given rule in the bridge.</li>
  1512. <li>createsensor &lt;name&gt; &lt;type&gt; &lt;uniqueid&gt; &lt;swversion&gt; &lt;modelid&gt;<br>
  1513. Creates a new CLIP (IP) sensor in the bridge.</li>
  1514. <li>deletesensor &lt;id&gt;<br>
  1515. Deletes the given sensor in the bridge and deletes the associated fhem device.</li>
  1516. <li>configsensor &lt;id&gt; &lt;json&gt;<br>
  1517. Write sensor config data.</li>
  1518. <li>setsensor &lt;id&gt; &lt;json&gt;<br>
  1519. Write CLIP sensor status data.</li>
  1520. <li>updatesensor &lt;id&gt; &lt;json&gt;<br>
  1521. Write sensor toplevel data.</li>
  1522. <li>deletewhitelist &lt;key&gt;<br>
  1523. Deletes the given key from the whitelist in the bridge.</li>
  1524. <li>touchlink<br>
  1525. perform touchlink action</li>
  1526. <li>checkforupdate<br>
  1527. perform checkforupdate action</li>
  1528. <li>statusRequest<br>
  1529. Update bridge status.</li>
  1530. <li>swupdate<br>
  1531. Update bridge firmware. This command is only available if a new firmware is
  1532. available (indicated by updatestate with a value of 2. The version and release date is shown in the reading swupdate.<br>
  1533. A notify of the form <code>define HUEUpdate notify bridge:swupdate.* {...}</code>
  1534. can be used to be informed about available firmware updates.<br></li>
  1535. <li>inactive<br>
  1536. inactivates the current device. note the slight difference to the
  1537. disable attribute: using set inactive the state is automatically saved
  1538. to the statefile on shutdown, there is no explicit save necesary.<br>
  1539. this command is intended to be used by scripts to temporarily
  1540. deactivate the harmony device.<br>
  1541. the concurrent setting of the disable attribute is not recommended.</li>
  1542. <li>active<br>
  1543. activates the current device (see inactive).</li>
  1544. </ul><br>
  1545. <a name="HUEBridge_Attr"></a>
  1546. <b>Attributes</b>
  1547. <ul>
  1548. <li><a href="#disable">disable</a></li>
  1549. <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
  1550. <li>httpUtils<br>
  1551. 0 -> use HttpUtils_BlockingGet<br>
  1552. 1 -> use HttpUtils_NonblockingGet<br>
  1553. not set -> use old module specific implementation</li>
  1554. <li>pollDevices<br>
  1555. 1 -> the bridge will poll all lights in one go instead of each device polling itself independently<br>
  1556. 2 -> the bridge will poll all devices in one go instead of each device polling itself independently<br>
  1557. default is 1.</li>
  1558. <li>createGroupReadings<br>
  1559. create 'artificial' readings for group devices.</li>
  1560. 0 -> create readings only for group devices where createGroupReadings ist set to 1
  1561. 1 -> create readings for all group devices where createGroupReadings ist not set or set to 1
  1562. undef -> do nothing
  1563. <li>queryAfterSet<br>
  1564. the bridge will request the real device state after a set command. default is 1.</li>
  1565. <li>noshutdown<br>
  1566. Some bridge devcies require a different type of connection handling. raspbee/deconz only works if the connection
  1567. is not immediately closed, the phillips hue bridge now shows the same behavior. so this is now the default. </li>
  1568. </ul><br>
  1569. </ul><br>
  1570. =end html
  1571. =cut