37_harmony.pm 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947
  1. # $Id: 37_harmony.pm 16299 2018-03-01 08:06:55Z justme1968 $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use Data::Dumper;
  6. use JSON;
  7. use MIME::Base64;
  8. use IO::Socket::INET;
  9. use Encode qw(encode_utf8);
  10. #use XML::Simple qw(:strict);
  11. use HttpUtils;
  12. my $harmony_isFritzBox = undef;
  13. sub
  14. harmony_isFritzBox()
  15. {
  16. $harmony_isFritzBox = int( qx( [ -f /usr/bin/ctlmgr_ctl ] && echo 1 || echo 0 ) ) if( !defined($harmony_isFritzBox) );
  17. return $harmony_isFritzBox;
  18. }
  19. sub
  20. harmony_decode_json($)
  21. {
  22. my ($data) = @_;
  23. return eval { decode_json($data) } if( harmony_isFritzBox() );
  24. return eval { JSON->new->utf8(0)->decode($data) };
  25. }
  26. sub
  27. harmony_Initialize($)
  28. {
  29. my ($hash) = @_;
  30. $hash->{ReadFn} = "harmony_Read";
  31. $hash->{DefFn} = "harmony_Define";
  32. $hash->{NotifyFn} = "harmony_Notify";
  33. $hash->{UndefFn} = "harmony_Undefine";
  34. $hash->{SetFn} = "harmony_Set";
  35. $hash->{GetFn} = "harmony_Get";
  36. $hash->{AttrFn} = "harmony_Attr";
  37. $hash->{AttrList} = "disable:1 nossl:1 $readingFnAttributes";
  38. $hash->{FW_detailFn} = "harmony_detailFn";
  39. $hash->{FW_showStatus} = 1;
  40. }
  41. #####################################
  42. sub
  43. harmony_Define($$)
  44. {
  45. my ($hash, $def) = @_;
  46. my @a = split("[ \t][ \t]*", $def);
  47. return "Usage: define <name> harmony [username password] ip" if(@a < 3 || @a > 5);
  48. return "Usage: define <name> harmony [username password] ip" if(@a == 4 && $a[2] ne "DEVICE" );
  49. delete( $hash->{helper}{username} );
  50. delete( $hash->{helper}{password} );
  51. my $name = $a[0];
  52. if( @a == 3 ) {
  53. my $ip = $a[2];
  54. $hash->{ip} = $ip;
  55. } elsif( @a == 4 ) {
  56. my $id = $a[3];
  57. return "$name: device '$id' already defined" if( defined($modules{$hash->{TYPE}}{defptr}{$id}) );
  58. $hash->{id} = $id;
  59. $modules{$hash->{TYPE}}{defptr}{$id} = $hash;
  60. } elsif( @a == 5 ) {
  61. my $username = harmony_encrypt($a[2]);
  62. my $password = harmony_encrypt($a[3]);
  63. my $ip = $a[4];
  64. $hash->{DEF} = "$username $password $ip";
  65. $hash->{helper}{username} = $username;
  66. $hash->{helper}{password} = $password;
  67. $hash->{ip} = $ip;
  68. }
  69. $hash->{NAME} = $name;
  70. $hash->{STATE} = "Initialized";
  71. $hash->{ConnectionState} = "Initialized";
  72. #$attr{$name}{nossl} = 1 if( !$init_done && harmony_isFritzBox() );
  73. $hash->{NOTIFYDEV} = "global";
  74. if( $init_done ) {
  75. harmony_connect($hash) if( !defined($hash->{id}) );
  76. }
  77. return undef;
  78. }
  79. sub
  80. harmony_Notify($$)
  81. {
  82. my ($hash,$dev) = @_;
  83. return if($dev->{NAME} ne "global");
  84. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  85. harmony_connect($hash) if( !defined($hash->{id}) );
  86. return undef;
  87. }
  88. sub
  89. harmony_Undefine($$)
  90. {
  91. my ($hash, $arg) = @_;
  92. if( defined($hash->{id}) ) {
  93. delete( $modules{$hash->{TYPE}}{defptr}{$hash->{id}} );
  94. return undef;
  95. }
  96. RemoveInternalTimer($hash);
  97. harmony_disconnect($hash);
  98. return undef;
  99. }
  100. sub
  101. harmony_detailFn()
  102. {
  103. my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
  104. my $hash = $defs{$d};
  105. return if( !defined( $hash->{discoveryinfo} ) );
  106. my $clientId = $hash->{discoveryinfo}->{setupSessionClient};
  107. $clientId =~ s/^\w*-//;
  108. my $hubIP = $hash->{discoveryinfo}->{ip};
  109. my $hubName = $hash->{discoveryinfo}->{friendlyName};
  110. $hubName =~ s/ /%20/;
  111. return "<a href=\"http://sl.dhg.myharmony.com/mobile/2/production/?locale=de-DE&clientId=$clientId&hubIP=$hubIP&hubName=$hubName&settings\" target=\"_blank\">myHarmony config</a><br>"
  112. }
  113. sub
  114. harmony_idOfActivity($$;$)
  115. {
  116. my ($hash, $label, $default) = @_;
  117. my $quoted_label = $label;
  118. $quoted_label =~ s/\./ /g;
  119. $quoted_label = quotemeta($quoted_label);
  120. foreach my $activity (@{$hash->{config}->{activity}}) {
  121. return $activity->{id} if( $activity->{label} =~ m/^$label$/ );
  122. return $activity->{id} if( $activity->{label} =~ m/^$quoted_label$/ );
  123. }
  124. return $default;
  125. }
  126. sub
  127. harmony_labelOfActivity($$;$)
  128. {
  129. my ($hash, $id, $default) = @_;
  130. foreach my $activity (@{$hash->{config}->{activity}}) {
  131. return $activity->{label} if( $activity->{id} == $id );
  132. }
  133. return $default;
  134. }
  135. sub
  136. harmony_activityOfId($$)
  137. {
  138. my ($hash, $id) = @_;
  139. foreach my $activity (@{$hash->{config}->{activity}}) {
  140. return $activity if( $activity->{id} == $id );
  141. }
  142. return undef;
  143. }
  144. sub
  145. harmony_idOfDevice($$;$)
  146. {
  147. my ($hash, $label, $default) = @_;
  148. my $quoted_label = $label;
  149. $quoted_label =~ s/\./ /g;
  150. $quoted_label = quotemeta($quoted_label);
  151. foreach my $device (@{$hash->{config}->{device}}) {
  152. return $device->{id} if( $device->{label} =~ m/^$label$/ );
  153. return $device->{id} if( $device->{label} =~ m/^$quoted_label$/ );
  154. }
  155. return $default;
  156. }
  157. sub
  158. harmony_labelOfDevice($$;$)
  159. {
  160. my ($hash, $id, $default) = @_;
  161. return undef if( $id eq '<unknown>' );
  162. return undef if( !defined($hash->{config}) );
  163. foreach my $device (@{$hash->{config}->{device}}) {
  164. return $device->{label} if( $device->{id} == $id );
  165. }
  166. return $default;
  167. }
  168. sub
  169. harmony_deviceOfId($$)
  170. {
  171. my ($hash, $id) = @_;
  172. return undef if( !defined($hash->{config}) );
  173. foreach my $device (@{$hash->{config}->{device}}) {
  174. return $device if( $device->{id} == $id );
  175. }
  176. return undef;
  177. }
  178. sub
  179. harmony_actionOfCommand($$)
  180. {
  181. my ($device, $command) = @_;
  182. return undef if( ref($device) ne "HASH" );
  183. $command = lc($command);
  184. foreach my $group (@{$device->{controlGroup}}) {
  185. foreach my $function (@{$group->{function}}) {
  186. #if( lc($function->{name}) eq $command ) {
  187. if( lc($function->{name}) =~ m/^$command$/ ) {
  188. return harmony_decode_json($function->{action});
  189. }
  190. }
  191. }
  192. return undef;
  193. }
  194. sub
  195. harmony_hubOfDevice($)
  196. {
  197. my ($id) = @_;
  198. foreach my $d (sort keys %defs) {
  199. next if( !defined($defs{$d}) );
  200. next if( $defs{$d}->{TYPE} ne "harmony" );
  201. next if( $defs{$d}->{id} );
  202. next if( !harmony_deviceOfId($defs{$d}, $id) );
  203. Log3 undef, 3, "harmony: IODev for device $id is $d" ;
  204. return $d;
  205. }
  206. }
  207. sub
  208. harmony_Set($$@)
  209. {
  210. my ($hash, $name, $cmd, @params) = @_;
  211. my ($param_a, $param_h) = parseParams(\@params);
  212. my ($param, $param2) = @{$param_a};
  213. #$cmd = lc( $cmd );
  214. my $list = "";
  215. if( defined($hash->{id}) ) {
  216. if( !$hash->{hub} ) {
  217. $hash->{hub} = harmony_hubOfDevice($hash->{id});
  218. return "no hub found for device $name ($hash->{id})" if( !$hash->{hub} );
  219. }
  220. if( $cmd ne "?" && !$param ) {
  221. $cmd = "PowerOn" if( $cmd eq "on" );
  222. $cmd = "PowerOff" if( $cmd eq "off" );
  223. my $device = harmony_deviceOfId( $defs{$hash->{hub}}, $hash->{id} );
  224. if( harmony_actionOfCommand( $device, $cmd ) ) {
  225. $param = $cmd;
  226. $cmd = "command";
  227. }
  228. }
  229. if( $cmd eq "command" ) {
  230. $param2 = $param;
  231. $param = $hash->{id};
  232. $hash = $defs{$hash->{hub}};
  233. } elsif( $cmd eq "hidDevice" || $cmd eq "text" || $cmd eq "cursor" || $cmd eq "special" || $cmd eq "hid" ) {
  234. my $id = $hash->{id};
  235. $hash = $defs{$hash->{hub}};
  236. my $device = harmony_deviceOfId( $hash, $id );
  237. return "unknown device" if( !$device );
  238. return "no keyboard associated with device $device->{label}" if( !$device->{IsKeyboardAssociated} );
  239. if( !$hash->{hidDevice} || $hash->{hidDevice} ne $id ) {
  240. $hash->{hidDevice} = $id;
  241. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='harmony.engine?sethiddevice' token=''>deviceId=$id</oa>");
  242. sleep( 3 );
  243. }
  244. return if( $cmd eq "hidDevice" );
  245. } else {
  246. $list = "command hidDevice:noArg text cursor:up,down,left,right,pageUp,pageDown,home,end special:previousTrack,nextTrack,stop,playPause,volumeUp,volumeDown,mute";
  247. #my $device = harmony_deviceOfId( $defs{$hash->{hub}}, $hash->{id} );
  248. #$list .= " on off" if( !defined($device->{isManualPower}) || $device->{isManualPower} eq "false" || !$device->{isManualPower} );
  249. return "Unknown argument $cmd, choose one of $list";
  250. }
  251. }
  252. if( $cmd ne "?" && !$param ) {
  253. if( $cmd eq 'off' ) {
  254. $cmd = "activity";
  255. $param = "-1";
  256. } elsif( my $activity = harmony_activityOfId($hash, $hash->{currentActivityID}) ) {
  257. if( harmony_actionOfCommand( $activity, $cmd ) ) {
  258. $param = $cmd;
  259. $cmd = "command";
  260. }
  261. }
  262. }
  263. if( $cmd eq 'activity' ) {
  264. $param = harmony_idOfActivity($hash, $param) if( $param && $param !~ m/^([\d-])+$/ );
  265. return "unknown activity" if( !$param );
  266. harmony_sendEngineGet($hash, "startactivity", "activityId=$param:timestamp=0");
  267. delete $hash->{channelAfterStart};
  268. $hash->{channelAfterStart} = $param2 if( $param2 );
  269. return undef;
  270. } elsif( $cmd eq "channel" ) {
  271. delete $hash->{channelAfterStart};
  272. return "no current activity" if( !defined($hash->{currentActivityID}) || $hash->{currentActivityID} == -1 );
  273. my $activity = harmony_activityOfId($hash, $hash->{currentActivityID});
  274. return "no device with 'channel changing role' in current activity $activity->{label}" if( !$activity->{isTuningDefault} );
  275. return "missing channel" if( !$param );
  276. harmony_sendEngineGet($hash, "changeChannel", "channel=$param:timestamp=0");
  277. return undef;
  278. } elsif( $cmd eq "command" ) {
  279. my $action;
  280. if( !$param2 ) {
  281. return "unknown activity" if( !$hash->{currentActivityID} );
  282. return "unknown command" if( !$param );
  283. my $activity = harmony_activityOfId($hash, $hash->{currentActivityID});
  284. return "unknown activity" if( !$activity );
  285. $action = harmony_actionOfCommand( $activity, $param );
  286. return "unknown command $param" if( !$action );
  287. } else {
  288. $param = harmony_idOfDevice($hash, $param) if( $param && $param !~ m/^([\d-])+$/ );
  289. return "unknown device" if( !$param );
  290. return "unknown command" if( !$param2 );
  291. my $device = harmony_deviceOfId( $hash, $param );
  292. return "unknown device" if( !$device );
  293. $action = harmony_actionOfCommand( $device, $param2 );
  294. return "unknown command $param2" if( !$action );
  295. }
  296. my $duration = $param_h->{duration};
  297. return "duration musst be numeric" if( defined($duration) && $duration !~ m/^([\d.-])+$/ );
  298. $duration = 0.1 if( !$duration || $duration < 0 );
  299. $duration = 5 if $duration > 5;
  300. Log3 $name, 4, "$name: sending $action->{command} for ${duration}s for ". harmony_labelOfDevice($hash, $action->{deviceId} );
  301. my $payload = "status=press:action={'command'::'$action->{command}','type'::'$action->{type}','deviceId'::'$action->{deviceId}'}:timestamp=0";
  302. harmony_sendEngineRender($hash, "holdAction", $payload);
  303. select(undef, undef, undef, ($duration));
  304. $payload = "status=release:action={'command'::'$action->{command}','type'::'$action->{type}','deviceId'::'$action->{deviceId}'}:timestamp=".$duration*1000;
  305. harmony_sendEngineRender($hash, "holdAction", $payload);
  306. return undef;
  307. } elsif( $cmd eq "getCurrentActivity" ) {
  308. harmony_sendEngineGet($hash, "getCurrentActivity");
  309. return undef;
  310. } elsif( $cmd eq "getConfig" ) {
  311. harmony_sendEngineGet($hash, "config");
  312. return undef;
  313. } elsif( $cmd eq "hidDevice" ) {
  314. my $id = $param;
  315. if( !$id && $hash->{currentActivityID} ) {
  316. my $activity = harmony_activityOfId($hash, $hash->{currentActivityID});
  317. return "unknown activity" if( !$activity );
  318. return "no device with 'keyboard text entry role' in current activity $activity->{label}" if( !$activity->{KeyboardTextEntryActivityRole} );
  319. $id = $activity->{KeyboardTextEntryActivityRole};
  320. } else {
  321. $id = harmony_idOfDevice($hash, $id) if( $id && $id !~ m/^([\d-])+$/ );
  322. return "unknown device $param" if( $param && !$id );
  323. my $device = harmony_deviceOfId( $hash, $id );
  324. return "unknown device" if( !$device );
  325. return "no keyboard associated with device $device->{label}" if( !$device->{IsKeyboardAssociated} );
  326. }
  327. $hash->{hidDevice} = $id;
  328. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='harmony.engine?sethiddevice' token=''>deviceId=$id</oa>");
  329. return undef;
  330. } elsif( $cmd eq "hid" || $cmd eq "text" || $cmd eq "cursor" || $cmd eq "special" ) {
  331. return "nothing to send" if( !$param );
  332. return "unknown activity" if( !$hash->{currentActivityID} );
  333. return "unknown command" if( !$param );
  334. if( !$hash->{hidDevice} ) {
  335. my $activity = harmony_activityOfId($hash, $hash->{currentActivityID});
  336. return "unknown activity" if( !$activity );
  337. return "no device with 'keyboard text entry role' in current activity $activity->{label}" if( !$activity->{KeyboardTextEntryActivityRole} );
  338. }
  339. if( $cmd eq "text" ) {
  340. $hash->{hid} = "" if( !$hash->{hid} );
  341. $hash->{hid} .= join(' ', @params);
  342. $param = undef;
  343. } elsif( $cmd eq "cursor" ) {
  344. $param = lc( $param ) if( $param );
  345. $param = "0700004A" if( $param eq "home" );
  346. $param = "0700004B" if( $param eq "pageup" );
  347. $param = "0700004D" if( $param eq "end" );
  348. $param = "0700004E" if( $param eq "pagedown" );
  349. $param = "0700004F" if( $param eq "right" );
  350. $param = "07000050" if( $param eq "left" );
  351. $param = "07000051" if( $param eq "down" );
  352. $param = "07000052" if( $param eq "up" );
  353. return "unknown cursor direction $param" if( $param !~ m/^07/ );
  354. } elsif( $cmd eq "special" ) {
  355. $param = lc( $param ) if( $param );
  356. $param = "01000081" if( $param eq "systempower" );
  357. $param = "01000082" if( $param eq "systemsleep" );
  358. $param = "01000083" if( $param eq "systemwake" );
  359. $param = "0C0000B5" if( $param eq "nexttrack" );
  360. $param = "0C0000B6" if( $param eq "previoustrack" );
  361. $param = "0C0000B7" if( $param eq "stop" );
  362. $param = "0C0000CD" if( $param eq "playpause" );
  363. $param = "0C0000E9" if( $param eq "volumeup" );
  364. $param = "0C0000EA" if( $param eq "volumedown" );
  365. $param = "0C0000E2" if( $param eq "mute" );
  366. return "unknown special key $param" if( $param !~ m/^0(1|C)/ );
  367. }
  368. harmony_sendHID($hash, $param);
  369. return undef;
  370. } elsif( $cmd eq "reconnect" ) {
  371. delete $hash->{helper}{UserAuthToken} if( $param && $param eq "all" );
  372. delete $hash->{identity} if( $param && $param eq "all" );
  373. harmony_connect($hash);
  374. return undef;
  375. } elsif( $cmd eq "autocreate" ) {
  376. return harmony_autocreate($hash,$param);
  377. return undef;
  378. } elsif( $cmd eq "sleeptimer" ) {
  379. my $interval = $param?$param*60:60*60;
  380. $interval = -1 if( $interval < 0 );
  381. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='harmony.engine?setsleeptimer'>interval=$interval</oa>", "setsleeptimer");
  382. return undef;
  383. } elsif( $cmd eq "sync" ) {
  384. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='setup.sync' token=''/>");
  385. return undef;
  386. } elsif( $cmd eq "update" ) {
  387. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logtech.setup/vnd.logitech.firmware?update' token=''>format=json</oa>");
  388. return undef;
  389. } elsif( $cmd eq "active" ) {
  390. return "can't activate disabled hub." if(AttrVal($name, "disable", undef));
  391. $hash->{ConnectionState} = "Disconnected";
  392. readingsSingleUpdate( $hash, "state", $hash->{ConnectionState}, 1 );
  393. harmony_connect($hash);
  394. return undef;
  395. } elsif( $cmd eq "inactive" ) {
  396. harmony_disconnect($hash);
  397. readingsSingleUpdate($hash, "state", "inactive", 1);
  398. return undef;
  399. } elsif( $cmd eq "xxx" ) {
  400. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logtech.setup/vnd.logitech.firmware?check' token=''>format=json</oa>");
  401. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logtech.setup/vnd.logitech.firmware?status' token=''>format=json</oa>");
  402. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logtech.setup/vnd.logitech.firmware?update' token=''>format=json</oa>");
  403. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='harmony.automation?notify' token=''></oa>");
  404. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='harmony.automation?getState' token=''></oa>");
  405. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='harmony.automation.state?notify' token=''></oa>");
  406. #harmony_sendIq($hash, "<query xmlns='jabber:iq:roster'/>");
  407. #harmony_sendIq($hash, "<query xmlns='http://jabber.org/protocol/disco#info'/>");
  408. #harmony_sendIq($hash, "<query xmlns='http://jabber.org/protocol/disco#items'/>");
  409. return undef;
  410. }
  411. if( $hash->{config} ) {
  412. return undef if( !defined($hash->{config}) );
  413. my $activities;
  414. if( $hash->{config}->{activity} ) {
  415. foreach my $activity (sort { ($a->{activityOrder}||0) <=> ($b->{activityOrder}||0) } @{$hash->{config}->{activity}}) {
  416. next if( $activity->{id} == -1 );
  417. $activities .= "," if( $activities );
  418. $activities .= $activity->{label};
  419. }
  420. }
  421. if( my $activity = harmony_activityOfId($hash, -1) ) {
  422. $activities .= "," if( $activities );
  423. $activities .= $activity->{label};
  424. }
  425. if( $activities ) {
  426. $activities =~ s/ /./g;
  427. $list .= " activity:$activities";
  428. }
  429. my $hidDevices;
  430. my $autocreateDevices;
  431. if( $hash->{config}->{device} ) {
  432. foreach my $device (sort { $a->{id} <=> $b->{id} } @{$hash->{config}->{device}}) {
  433. if( $device->{IsKeyboardAssociated} ) {
  434. $hidDevices .= "," if( $hidDevices );
  435. $hidDevices .= harmony_labelOfDevice($hash, $device->{id} );
  436. }
  437. if( !defined($modules{$hash->{TYPE}}{defptr}{$device->{id}}) ) {
  438. $autocreateDevices .= "," if( $autocreateDevices );
  439. $autocreateDevices .= harmony_labelOfDevice($hash, $device->{id} );
  440. }
  441. }
  442. }
  443. if( $hidDevices ) {
  444. $hidDevices =~ s/ /./g;
  445. $list .= " hidDevice:,$hidDevices";
  446. }
  447. if( $autocreateDevices ) {
  448. $autocreateDevices =~ s/ /./g;
  449. $list .= " autocreate:$autocreateDevices,";
  450. }
  451. }
  452. $list .= " channel" if( defined($hash->{currentActivityID}) && $hash->{currentActivityID} != -1 );
  453. $list .= " command active:noArg inactive:noArg getConfig:noArg getCurrentActivity:noArg off:noArg reconnect:noArg sleeptimer sync:noArg text cursor:up,down,left,right,pageUp,pageDown,home,end special:previousTrack,nextTrack,stop,playPause,volumeUp,volumeDown,mute";
  454. $list .= " update:noArg" if( $hash->{hubUpdate} );
  455. return "Unknown argument $cmd, choose one of $list";
  456. }
  457. sub
  458. harmony_getLoginToken($)
  459. {
  460. my ($hash) = @_;
  461. #return if( defined($hash->{helper}{UserAuthToken}) );
  462. if( 0 ) {
  463. my $https = "https";
  464. $https = "http" if( AttrVal($hash->{NAME}, "nossl", 0) );
  465. my $json = encode_json( { clientId => '',
  466. clientTypeId => 'ControlApp' } );
  467. my($err,$data) = HttpUtils_BlockingGet({
  468. url => "$https://svcs.myharmony.com/discovery/Discovery.svc/json/GetJson2Uris",
  469. timeout => 10,
  470. #noshutdown => 1,
  471. #httpversion => "1.1",
  472. header => "Content-Type: application/json;charset=utf-8",
  473. data => $json,
  474. });
  475. harmony_dispatch( {hash=>$hash,type=>'GetJson2Uris'},$err,$data );
  476. }
  477. return if( defined($hash->{helper}{UserAuthToken}) );
  478. if( 1 || !$hash->{helper}{username} ) {
  479. $hash->{helper}{UserAuthToken} = "";
  480. return;
  481. }
  482. my $https = "https";
  483. $https = "http" if( AttrVal($hash->{NAME}, "nossl", 0) );
  484. my $json = encode_json( { email => harmony_decrypt($hash->{helper}{username}),
  485. password => harmony_decrypt($hash->{helper}{password}) } );
  486. my($err,$data) = HttpUtils_BlockingGet({
  487. url => "$https://svcs.myharmony.com/CompositeSecurityServices/Security.svc/json/GetUserAuthToken",
  488. timeout => 10,
  489. #noshutdown => 1,
  490. #httpversion => "1.1",
  491. header => "Content-Type: application/json;charset=utf-8",
  492. data => $json,
  493. });
  494. harmony_dispatch( {hash=>$hash,type=>'token'},$err,$data );
  495. }
  496. sub
  497. harmony_attr2hash($)
  498. {
  499. my ($attr) = @_;
  500. my @args = split(' ', $attr);
  501. my %params = ();
  502. while (@args) {
  503. my $arg = shift(@args);
  504. my ($name,$value) = split("=", $arg,2);
  505. while( $value && $value =~ m/^'/ && $value !~ m/'$/ ) {
  506. my $next = shift(@args);
  507. last if( !defined($next) );
  508. $value .= " ". $next;
  509. }
  510. $params{$name} = substr( $value, 1, -1 );
  511. }
  512. return \%params;
  513. }
  514. sub
  515. harmony_CDATA2hash($)
  516. {
  517. my ($cdata) = @_;
  518. my @args = split(':', $cdata);
  519. my %params = ();
  520. while (@args) {
  521. my $arg = shift(@args);
  522. my ($name,$value) = split("=", $arg,2);
  523. #fix for updates=table: 0x...
  524. if( $args[0] && $args[0] !~ m/=/ ) {
  525. my $next = shift(@args);
  526. last if( !defined($next) );
  527. $value .= ":". $next;
  528. }
  529. ##fix for http://...
  530. #if( $args[0] && $args[0] =~ m/^\/\// ) {
  531. # my $next = shift(@args);
  532. # last if( !defined($next) );
  533. # $value .= ":". $next;
  534. #}
  535. #fix for json {...<key>:<value>...}
  536. while( $value && $value =~ m/^{/ && $value !~ m/}$/ ) {
  537. my $next = shift(@args);
  538. last if( !defined($next) );
  539. $value .= ":". $next;
  540. }
  541. $params{$name} = $value if( $name );
  542. }
  543. return \%params;
  544. }
  545. use constant { CTRL => 0x01,
  546. SHIFT => 0x02,
  547. ALT => 0x04,
  548. GUI => 0x08,
  549. RIGHT_CTRL => 0x10,
  550. RIGHT_SHIFT => 0x20,
  551. RIGHT_ALT => 0x40,
  552. RIGHT_GUI => 0x80,
  553. };
  554. my %keys = ( '1' => '0702001E',
  555. '2' => '0702001F',
  556. '3' => '07020020',
  557. '4' => '07020021',
  558. '5' => '07020022',
  559. '6' => '07020023',
  560. '7' => '07020024',
  561. '8' => '07020025',
  562. '9' => '07020026',
  563. '0' => '07020027',
  564. '\\n'=> '07000028',
  565. '\\e'=> '07000029',
  566. '\\t'=> '0700002B',
  567. ' ' => '0700002C',
  568. '!' => '0702001E',
  569. '"' => '0702001F',
  570. '§' => '07020020',
  571. '$' => '07020021',
  572. '%' => '07020022',
  573. '&' => '07020023',
  574. '/' => '07020024',
  575. '(' => '07020025',
  576. ')' => '07020026',
  577. '=' => '07020027',
  578. 'ß' => '0700002D',
  579. '´' => '0700002E',
  580. 'ü' => '0700002F',
  581. '+' => '07000030',
  582. '#' => '07000031',
  583. 'ö' => '07000033',
  584. 'ä' => '07000034',
  585. '<' => '07000035',
  586. ',' => '07000036',
  587. '.' => '07000037',
  588. '-' => '07000038',
  589. '?' => '0702002D',
  590. '`' => '0702002E',
  591. 'Ü' => '0702002F',
  592. '*' => '07020030',
  593. "'" => '07020031',
  594. 'Ö' => '07020033',
  595. 'Ä' => '07020034',
  596. '>' => '07020035',
  597. ';' => '07020036',
  598. ':' => '07020037',
  599. '_' => '07020038',
  600. 'F1' => '0700003A',
  601. 'F2' => '0700003B',
  602. 'F3' => '0700003C',
  603. 'F4' => '0700003D',
  604. 'F5' => '0700003E',
  605. 'F6' => '0700003F',
  606. 'F7' => '07000040',
  607. 'F8' => '07000041',
  608. 'F9' => '07000042',
  609. 'F10' => '07000043',
  610. 'F11' => '07000044',
  611. 'F12' => '07000045',
  612. 'KP/' => '07000054',
  613. 'KP*' => '07000055',
  614. 'KP-' => '07000056',
  615. 'KP+' => '07000057',
  616. 'KP\\n' => '07000058',
  617. 'KP1' => '07000059',
  618. 'KP2' => '0700005A',
  619. 'KP3' => '0700005C',
  620. 'KP4' => '0700005C',
  621. 'KP5' => '0700005D',
  622. 'KP6' => '0700005E',
  623. 'KP7' => '0700005F',
  624. 'KP8' => '07000060',
  625. 'KP9' => '07000061',
  626. 'KP0' => '07000062',
  627. );
  628. sub
  629. harmony_char2hid($)
  630. {
  631. my ($char) = @_;
  632. my $ret;
  633. if( $char ge '1' && $char le '9' ) {
  634. $ret = sprintf( "070000%02X", 0x1E + ord($char) - ord('1') );
  635. } elsif( $char ge 'a' && $char le 'z' ) {
  636. $ret = sprintf( "070000%02X", 0x04 + ord($char) - ord('a') );
  637. } elsif( $char ge 'A' && $char le 'Z' ) {
  638. $ret = sprintf( "070200%02X", 0x04 + ord($char) - ord('A') );
  639. } elsif( defined( $keys{$char} ) ) {
  640. $ret = $keys{$char};
  641. }
  642. return $ret;
  643. }
  644. sub
  645. harmony_updateActivity($$;$)
  646. {
  647. my ($hash,$id,$modifier) = @_;
  648. $modifier = "" if( !$modifier );
  649. if( $hash->{currentActivityID} && $hash->{currentActivityID} ne $id ) {
  650. my $id = $hash->{currentActivityID};
  651. $hash->{previousActivityID} = $id;
  652. my $previous = harmony_labelOfActivity($hash,$id,$id);
  653. readingsSingleUpdate( $hash, "previousActivity", $previous, 0 );
  654. }
  655. if( !$modifier && defined($modules{$hash->{TYPE}}{defptr}) ) {
  656. if( my $activity = harmony_activityOfId($hash, $id)) {
  657. foreach my $id (keys %{$activity->{fixit}}) {
  658. if( my $hash = $modules{$hash->{TYPE}}{defptr}{$id} ) {
  659. my $state = $activity->{fixit}->{$id}->{Power};
  660. $state = "Manual" if( !$state );
  661. readingsSingleUpdate( $hash, "power", lc($state), 1 );
  662. }
  663. }
  664. }
  665. }
  666. $hash->{currentActivityID} = $id;
  667. my $activity = harmony_labelOfActivity($hash,$id,$id);
  668. readingsSingleUpdate( $hash, "currentActivity", "$modifier$activity", 1 );
  669. $activity =~ s/ /./g;
  670. if( !$modifier && $activity ne ReadingsVal($hash->{NAME},"activity", "" ) ) {
  671. readingsSingleUpdate( $hash, "activity", $activity, 1 );
  672. harmony_sendEngineGet($hash, "changeChannel", "channel=$hash->{channelAfterStart}:timestamp=0") if( $hash->{channelAfterStart} );
  673. delete $hash->{channelAfterStart};
  674. }
  675. delete $hash->{hidDevice} if( $id == -1 );
  676. }
  677. sub
  678. harmony_Read($)
  679. {
  680. my ($hash) = @_;
  681. my $name = $hash->{NAME};
  682. my $buf;
  683. my $ret = sysread($hash->{CD}, $buf, 1024*1024);
  684. if(!defined($ret) || $ret <= 0) {
  685. harmony_disconnect( $hash );
  686. InternalTimer(gettimeofday()+2, "harmony_connect", $hash, 0);
  687. return;
  688. }
  689. my $data = $hash->{helper}{PARTIAL};
  690. $data .= $buf;
  691. #FIXME: should use real xmpp/xml parser
  692. # see forum https://forum.fhem.de/index.php/topic,14163.msg575033.html#msg575033
  693. $data =~ s/<iq\/>//g;
  694. $data =~ s/<\/iq><iq/<\/iq>\n<iq/g;
  695. my @lines = split( "\n", $data );
  696. foreach my $line (@lines) {
  697. if( $line =~ m/^<(\w*)\s*([^>]*)?\/>(.*)?/ ) {
  698. Log3 $name, 5, "$name: tag: $1, attr: $2";
  699. $data = $3;
  700. if( $line eq "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>" ) {
  701. $hash->{ConnectionState} = "LoggedIn";
  702. if( $hash->{helper}{UserAuthToken} ) {
  703. harmony_getSessionToken($hash);
  704. } else {
  705. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logitech.connect/vnd.logitech.ping?get' token=''></oa>");
  706. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logitech.harmony/vnd.logitech.harmony.system?systeminfo' token=''></oa>");
  707. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logitech.connect/vnd.logitech.deviceinfo?get' token=''></oa>");
  708. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logitech.setup/vnd.logitech.account?getProvisionInfo' token=''></oa>");
  709. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logitech.connect/vnd.logitech.statedigest?get' token=''>format=json</oa>");
  710. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logtech.setup/vnd.logitech.firmware?check' token=''>format=json</oa>");
  711. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logtech.setup/vnd.logitech.firmware?status' token=''>format=json</oa>");
  712. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logtech.setup/vnd.logitech.firmware?update' token=''>format=json</oa>");
  713. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='proxy.resource?get' token=''>hetag= :uri=dynamite:://HomeAutomationService/Config/:encode=true</oa>");
  714. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='harmony.automation?getstate' token=''></oa>");
  715. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logitech.connect/vnd.logitech.pair'>name=1vm7ATw/tN6HXGpQcCs/A5MkuvI#iOS6.0.1#iPhone</oa>");
  716. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='connect.discoveryinfo?get'>format=json</oa>");
  717. #harmony_sendEngineGet($hash, "config");
  718. }
  719. RemoveInternalTimer($hash);
  720. InternalTimer(gettimeofday()+50, "harmony_ping", $hash, 0);
  721. }
  722. $line = $3;
  723. }
  724. if( $line =~ m/^<(\w*)([^>]*)>(.*)<\/\1>(.*)?/ ) {
  725. Log3 $name, 5, "$name: tag: $1, attr: $2";
  726. #Log3 $name, 5, " data: $3";
  727. $data = $4;
  728. my $tag = $1;
  729. my $attr = $2;
  730. my $content = $3;
  731. #if( $content =~ m/^<(\w*)([^>]*)>(.*)<\/\1>(.*)?/ ) {
  732. # Log3 $name, 1, "$name: tag: $1, attr: $2";
  733. # Log3 $name, 1, Dumper harmony_attr2hash($2);
  734. #}
  735. if( $content =~ m/<!\[CDATA\[(.*)\]\]>/ ) {
  736. my $cdata = $1;
  737. my $json;
  738. my $decoded;
  739. if( $cdata =~ m/^{.*}$/ ) {
  740. $json = harmony_decode_json($cdata);
  741. $decoded = $json;
  742. } else {
  743. $decoded = harmony_CDATA2hash($cdata);
  744. }
  745. my $error = $decoded->{errorCode};
  746. if( $error && $error != 200 ) {
  747. Log3 $name, 2, "$name: error ($error): $decoded->{errorString}";
  748. }
  749. if( ($tag eq "iq" && $content =~ m/statedigest\?get'/)
  750. || ($tag eq "message" && $content =~ m/type="connect.stateDigest\?notify"/) ) {
  751. Log3 $name, 4, "$name: statedigest: $cdata";
  752. if( $decoded ) {
  753. if( defined($decoded->{syncStatus}) ) {
  754. harmony_sendEngineGet($hash, "config") if( $hash->{syncStatus} && !$decoded->{syncStatus} );
  755. $hash->{syncStatus} = $decoded->{syncStatus};
  756. }
  757. if( defined($decoded->{hubUpdate}) && $decoded->{hubUpdate} eq "true" && !$hash->{hubUpdate} ) {
  758. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logtech.setup/vnd.logitech.firmware?check' token=''>format=json</oa>");
  759. }
  760. $hash->{activityStatus} = $decoded->{activityStatus} if( defined($decoded->{activityStatus}) );
  761. $hash->{hubSwVersion} = $decoded->{hubSwVersion} if( defined($decoded->{hubSwVersion}) );
  762. $hash->{hubUpdate} = ($decoded->{hubUpdate} eq 'true'?1:0) if( defined($decoded->{hubUpdate}) );
  763. my $modifier = "";
  764. $modifier = "starting " if( $hash->{activityStatus} == 1 );
  765. $modifier = "stopping " if( $hash->{activityStatus} == 3 );
  766. harmony_updateActivity($hash, $decoded->{activityId}, $modifier) if( defined($decoded->{activityId}) );
  767. if( defined($decoded->{sleepTimerId}) ) {
  768. if( $decoded->{sleepTimerId} == -1 ) {
  769. delete $hash->{sleeptimer};
  770. DoTrigger( $name, "sleeptimer: expired" );
  771. } else {
  772. harmony_sendEngineGet($hash, "gettimerinterval", "timerId=$decoded->{sleepTimerId}");
  773. }
  774. }
  775. }
  776. } elsif( $tag eq "message" ) {
  777. if( $content =~ m/type="harmony.engine\?startActivityFinished"/ ) {
  778. if( my $id = $decoded->{activityId} ) {
  779. if( harmony_activityOfId($hash, $id) ) {
  780. if( $id == -1 && $hash->{helper}{ignorePowerOff} ) {
  781. delete $hash->{helper}{ignorePowerOff};
  782. } else {
  783. harmony_updateActivity($hash, $id);
  784. }
  785. } else {
  786. $hash->{helper}{ignorePowerOff} = 1;
  787. }
  788. }
  789. } elsif( $content =~ m/type="vnd.logitech.harmony\/vnd.logitech.control.button\?pressType"/ ) {
  790. DoTrigger( $name, "vnd.logitech.control.button: $decoded->{type}" );
  791. } elsif( $content =~ m/type="automation.state\?notify"/ ) {
  792. DoTrigger( $name, "automation.state: $cdata" );
  793. } else {
  794. Log3 $name, 4, "$name: unknown message: $content";
  795. }
  796. } elsif( $tag eq "iq" ) {
  797. if( $content =~ m/errorcode='(\d*)'.*errorstring='(.*)'/ && $1 != 100 && $1 != 200 ) {
  798. Log3 $name, 2, "$name: error ($1): $2";
  799. } elsif( $content =~ m/vnd.logitech.pair/ ) {
  800. if( !$hash->{identity} && $decoded->{identity} ) {
  801. $hash->{identity} = $decoded->{identity};
  802. harmony_connect($hash);
  803. } else {
  804. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='connect.discoveryinfo?get'>format=json</oa>");
  805. #harmony_sendEngineGet($hash, "config");
  806. }
  807. } elsif( $content =~ m/\?startactivity/i ) {
  808. if( $cdata =~ m/done=(\d*):total=(\d*)(:deviceId=(\d*))?/ ) {
  809. my $done = $1;
  810. my $total = $2;
  811. my $id = $4;
  812. $id = "<unknown>" if( !defined($id) );
  813. my $label = harmony_labelOfDevice($hash,$id,$id);
  814. if( $done == $total ) {
  815. Log3 $name, 4, "$name: done starting/stopping device: $label";
  816. } elsif( $done == 1 ) {
  817. Log3 $name, 4, "$name: starting/stopping device: $label";
  818. } else {
  819. Log3 $name, 4, "$name: starting/stopping device ($done/$total): $label";
  820. }
  821. } else {
  822. Log3 $name, 3, "$name: unknown startactivity message: $content";
  823. }
  824. } elsif( $content =~ m/discoveryinfo\?get/ && $decoded ) {
  825. Log3 $name, 4, "$name: ". Dumper $decoded;
  826. $hash->{discoveryinfo} = $decoded;
  827. #$hash->{current_fw_version} = $decoded->{current_fw_version} if( defined($decoded->{current_fw_version}) );
  828. harmony_sendEngineGet($hash, "config");
  829. } elsif( $content =~ m/engine\?changeChannel/ && $decoded ) {
  830. } elsif( $content =~ m/engine\?gettimerinterval/ && $decoded ) {
  831. $hash->{sleeptimer} = FmtDateTime( gettimeofday() + $decoded->{interval} );
  832. DoTrigger( $name, "sleeptimer: $hash->{sleeptimer}" );
  833. } elsif( $content =~ m/firmware\?/ && $decoded ) {
  834. Log3 $name, 4, "$name: firmware: $cdata";
  835. if( $decoded->{status} && $decoded->{newVersion} ) {
  836. $hash->{newVersion} = $decoded->{newVersion};
  837. my $txt = $decoded->{newVersion};
  838. $txt .= ", isCritical: $decoded->{isCritical}";
  839. $txt .= ", bytes: $decoded->{totalBytes}";
  840. readingsSingleUpdate( $hash, "newVersion", $txt, 1 ) if( $txt ne ReadingsVal($hash->{NAME},"newVersion", "" ) );
  841. } else {
  842. delete $hash->{newVersion};
  843. }
  844. } elsif( $content =~ m/\?config/ && $decoded ) {
  845. $hash->{config} = $decoded;
  846. Log3 $name, 3, "$name: new config ";
  847. #Log3 $name, 5, "$name: ". Dumper $json;
  848. #my $station = $hash->{config}->{content}->{contentImageHost};
  849. #$station =~ s/{stationId}/4faa0c3b7232c50c26001b86/;
  850. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='proxy.resource?get' token=''>hetag= :uri=content:://1.0/user;$station:encode=true</oa>");
  851. #foreach my $device (sort { $a->{id} <=> $b->{id} } @{$hash->{config}->{device}}) {
  852. # my $content = $hash->{config}->{content}->{contentDeviceHost};
  853. # $content =~ s/{deviceProfileUri}/$device->{deviceProfileUri}/;
  854. # harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='proxy.resource?get' token=''>hetag= :uri=content:://1.0/user;$content:encode=true</oa>");
  855. # last;
  856. #}
  857. #harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='proxy.resource?get' token=''>hetag= :uri=content:://1.0/user;$hash->{config}->{content}->{householdUserProfileUri}:encode=true</oa>");
  858. harmony_sendIq($hash, "<oa xmlns='connect.logitech.com' mime='vnd.logitech.connect/vnd.logitech.statedigest?get' token=''>format=json</oa>");
  859. #harmony_sendEngineGet($hash, "getCurrentActivity");
  860. } elsif( $cdata =~ m/result=(.*)/ ) {
  861. my $result = $1;
  862. Log3 $name, 4, "$name: got result $1";
  863. if( $content =~ m/getCurrentActivity/ ) {
  864. harmony_updateActivity($hash, $result);
  865. } else {
  866. Log3 $name, 3, "$name: unknown result: $content";
  867. }
  868. } elsif( $content =~ m/mime='hid.report'/ ) {
  869. harmony_sendHID($hash) if( $hash->{hid} );
  870. } else {
  871. Log3 $name, 3, "$name: unknown iq: $content";
  872. Log 3, Dumper $decoded;
  873. Log 3, Dumper harmony_decode_json($decoded->{resource}) if( !$json && $decoded->{resource} && $decoded->{resource} =~ m/^{.*}$/ );
  874. }
  875. } else {
  876. Log3 $name, 3, "$name: unhandled tag: $line";
  877. }
  878. } elsif( $content =~ m/mime='hid.report'/ ) {
  879. harmony_sendHID($hash) if( $hash->{hid} );
  880. } elsif( $line =~ m/<iq id='ping-(\d+)' type='result'><\/iq>/ ) {
  881. Log3 $name, 5, "$name: got ping response $1";
  882. } elsif( $line ) {
  883. Log3 $name, 4, "$name: unknown (no cdata): $line";
  884. }
  885. } elsif( $line =~ m/^<\?xml.*id='([\w-]*).*error.*>/ ) {
  886. Log3 $name, 2, "$name: error: $1" if( $1 );
  887. Log3 $name, 4, "$name: $line";
  888. harmony_disconnect($hash);
  889. } elsif( $line =~ m/^<\?xml.*PLAIN.*>/ ) {
  890. my $identity = $hash->{identity}?$hash->{identity}:"guest";
  891. my $auth = encode_base64("\0$identity\@connect.logitech.com\0gatorade.",'');
  892. harmony_send($hash, "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>$auth</auth>");
  893. $data = "";
  894. } elsif( $line =~ m/^<.*>$/ ) {
  895. Log3 $name, 4, "$name: unknown: $line";
  896. } elsif( $line ) {
  897. #Log3 $name, 5, "$name: $line";
  898. }
  899. }
  900. $hash->{helper}{PARTIAL} = $data;
  901. #Log 3, "length: ". length($hash->{helper}{PARTIAL});
  902. }
  903. sub
  904. harmony_disconnect($)
  905. {
  906. my ($hash) = @_;
  907. my $name = $hash->{NAME};
  908. RemoveInternalTimer($hash);
  909. $hash->{ConnectionState} = "Disconnected";
  910. readingsSingleUpdate( $hash, "state", $hash->{ConnectionState}, 1 ) if( $hash->{ConnectionState} ne ReadingsVal($name, "state", "" ) );
  911. return if( !$hash->{CD} );
  912. Log3 $name, 2, "$name: disconnect";
  913. close($hash->{CD}) if($hash->{CD});
  914. delete($hash->{FD});
  915. delete($hash->{CD});
  916. delete($selectlist{$name});
  917. $hash->{LAST_DISCONNECT} = FmtDateTime( gettimeofday() );
  918. }
  919. sub
  920. harmony_connect($)
  921. {
  922. my ($hash) = @_;
  923. my $name = $hash->{NAME};
  924. return if( IsDisabled($name) );
  925. harmony_disconnect($hash);
  926. Log3 $name, 4, "$name: connect";
  927. harmony_getLoginToken($hash);
  928. if( defined($hash->{helper}{UserAuthToken}) ) {
  929. my $timeout = $hash->{TIMEOUT} ? $hash->{TIMEOUT} : 3;
  930. my $conn = IO::Socket::INET->new(PeerAddr => "$hash->{ip}:5222", Timeout => $timeout);
  931. if( $conn ) {
  932. Log3 $name, 3, "$name: connected";
  933. $hash->{ConnectionState} = "Connected";
  934. readingsSingleUpdate( $hash, "state", $hash->{ConnectionState}, 1 ) if( $hash->{ConnectionState} ne ReadingsVal($name, "state", "" ) );
  935. $hash->{LAST_CONNECT} = FmtDateTime( gettimeofday() );
  936. $hash->{FD} = $conn->fileno();
  937. $hash->{CD} = $conn; # sysread / close won't work on fileno
  938. $hash->{CONNECTS}++;
  939. $selectlist{$name} = $hash;
  940. $hash->{helper}{PARTIAL} = "";
  941. harmony_send($hash, "<stream:stream to='connect.logitech.com' xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client' xml:lang='en' version='1.0'>");
  942. } else {
  943. harmony_disconnect( $hash );
  944. InternalTimer(gettimeofday()+10, "harmony_connect", $hash, 0);
  945. }
  946. }
  947. }
  948. sub
  949. harmony_getSessionToken($)
  950. {
  951. my ($hash) = @_;
  952. my $name = $hash->{NAME};
  953. my $unique_id = '1vm7ATw/tN6HXGpQcCs/A5MkuvI';
  954. my $device = 'iOS6.0.1#iPhone';
  955. harmony_sendPair($hash, "token=$hash->{helper}{UserAuthToken}:name=$unique_id#$device");
  956. }
  957. sub
  958. harmony_send($$)
  959. {
  960. my ($hash, $data) = @_;
  961. my $name = $hash->{NAME};
  962. return undef if( !$hash->{CD} );
  963. Log3 $name, 4, "$name: send: $data";
  964. syswrite $hash->{CD}, $data;
  965. }
  966. my $id = 0;
  967. sub
  968. harmony_sendIq($$;$)
  969. {
  970. my ($hash, $xml, $type) = @_;
  971. $type = 'get' if ( !$type );
  972. ++$id;
  973. my $iq = "<iq type='$type' id='$id' from='guest'>$xml</iq>";
  974. $iq = "<iq type='$type' id='$id'>$xml</iq>";
  975. harmony_send($hash,$iq);
  976. }
  977. sub
  978. harmony_sendPair($$)
  979. {
  980. my ($hash, $payload) = @_;
  981. $payload = '' if ( !$payload );
  982. my $xml = "<oa xmlns='connect.logitech.com' mime='vnd.logitech.connect/vnd.logitech.pair'>$payload</oa>";
  983. harmony_sendIq($hash,$xml);
  984. }
  985. sub
  986. harmony_sendEngineGet($$;$)
  987. {
  988. my ($hash, $endpoint, $payload) = @_;
  989. $payload = '' if ( !$payload );
  990. my $xml = "<oa xmlns='connect.logitech.com' mime='vnd.logitech.harmony/vnd.logitech.harmony.engine?$endpoint'>$payload</oa>";
  991. harmony_sendIq($hash,$xml);
  992. }
  993. sub
  994. harmony_sendHID($;$)
  995. {
  996. my ($hash, $code) = @_;
  997. if( !$code ) {
  998. return if( !$hash->{hid} );
  999. my $char = substr($hash->{hid}, 0, 1);
  1000. $hash->{hid} = substr($hash->{hid}, 1);
  1001. if( $char eq '\\' || ord($char) == 0xC3 ) {
  1002. $char .= substr($hash->{hid}, 0, 1);
  1003. $hash->{hid} = substr($hash->{hid}, 1);
  1004. }
  1005. $code = harmony_char2hid( $char );
  1006. }
  1007. my $xml = "<oa xmlns='connect.logitech.com' mime='hid.report' token=''>{'code':'$code'}</oa>";
  1008. harmony_sendIq($hash,$xml);
  1009. }
  1010. sub
  1011. harmony_sendEngineRender($$$)
  1012. {
  1013. my ($hash, $endpoint, $payload) = @_;
  1014. #my $xml = "<oa xmlns='connect.logitech.com' mime='vnd.logitech.harmony/vnd.logitech.harmony.engine?$endpoint' token=''>$payload</oa>";
  1015. my $xml = "<oa xmlns='connect.logitech.com' mime='vnd.logitech.harmony/vnd.logitech.harmony.engine?$endpoint'>$payload</oa>";
  1016. harmony_sendIq($hash,$xml, "render");
  1017. }
  1018. sub
  1019. harmony_ping($)
  1020. {
  1021. my( $hash ) = @_;
  1022. return if( $hash->{ConnectionState} eq "Disconnected" );
  1023. ++$id;
  1024. harmony_send($hash, "<iq type='get' id='ping-$id'><ping xmlns='urn:xmpp:ping'/></iq>");
  1025. RemoveInternalTimer($hash);
  1026. InternalTimer(gettimeofday()+50, "harmony_ping", $hash, 0);
  1027. }
  1028. sub
  1029. harmony_dispatch($$$)
  1030. {
  1031. my ($param, $err, $data) = @_;
  1032. my $hash = $param->{hash};
  1033. my $name = $hash->{NAME};
  1034. if( $err ) {
  1035. Log3 $name, 2, "$name: http request failed: $err";
  1036. } elsif( $data ) {
  1037. Log3 $name, 4, "$name: $data";
  1038. if( $data !~ m/^{.*}$/ ) {
  1039. Log3 $name, 2, "$name: invalid json detected: $data";
  1040. return undef;
  1041. }
  1042. my $json = harmony_decode_json($data);
  1043. if( $param->{type} eq 'token' ) {
  1044. harmony_parseToken($hash, $json);
  1045. } elsif( $param->{type} eq 'GetJson2Uris' ) {
  1046. Log 1, Dumper $json;
  1047. }
  1048. }
  1049. }
  1050. sub
  1051. harmony_autocreate($;$)
  1052. {
  1053. my($hash, $param) = @_;
  1054. my $name = $hash->{NAME};
  1055. return if( !defined($hash->{config}) );
  1056. my $id = $param;
  1057. $id = harmony_idOfDevice($hash, $id) if( $id && $id !~ m/^([\d-])+$/ );
  1058. return "unknown device $param" if( $param && !$id );
  1059. #foreach my $d (keys %defs) {
  1060. # next if($defs{$d}{TYPE} ne "autocreate");
  1061. # return undef if( IsDisabled($defs{$d}{NAME} ) );
  1062. #}
  1063. my $autocreated = 0;
  1064. foreach my $device (@{$hash->{config}->{device}}) {
  1065. next if( $id && $device->{id} != $id );
  1066. if( defined($modules{$hash->{TYPE}}{defptr}{$device->{id}}) ) {
  1067. Log3 $name, 4, "$name: device '$device->{id}' already defined";
  1068. next;
  1069. }
  1070. my $devname = "harmony_". $device->{id};
  1071. my $define = "$devname harmony DEVICE $device->{id}";
  1072. Log3 $name, 3, "$name: create new device '$devname' for device '$device->{id}'";
  1073. my $cmdret = CommandDefine(undef,$define);
  1074. if($cmdret) {
  1075. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$device->{id}': $cmdret";
  1076. } else {
  1077. $cmdret = CommandAttr(undef,"$devname alias $device->{label}") if( defined($device->{label}) );
  1078. $cmdret = CommandAttr(undef,"$devname event-on-change-reading .*");
  1079. $cmdret = CommandAttr(undef,"$devname room $hash->{TYPE}");
  1080. $cmdret = CommandAttr(undef,"$devname stateFormat power");
  1081. #$cmdret = CommandAttr(undef,"$devname IODev $name");
  1082. $autocreated++;
  1083. }
  1084. }
  1085. CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) );
  1086. return "created $autocreated devices";
  1087. }
  1088. sub
  1089. harmony_parseToken($$)
  1090. {
  1091. my($hash, $json) = @_;
  1092. my $name = $hash->{NAME};
  1093. RemoveInternalTimer($hash);
  1094. my $error = $json->{ErrorCode};
  1095. if( $error && $error != 200 ) {
  1096. Log3 $name, 2, "$name: error ($error): $json->{Message}";
  1097. $hash->{lastError} = $json->{Message};
  1098. }
  1099. my $had_token = $hash->{helper}{UserAuthToken};
  1100. $hash->{helper}{AccountId} = $json->{GetUserAuthTokenResult}->{AccountId};
  1101. $hash->{helper}{UserAuthToken} = $json->{GetUserAuthTokenResult}->{UserAuthToken};
  1102. if( $hash->{helper}{UserAuthToken} ) {
  1103. $hash->{ConnectionState} = "GotToken";
  1104. } else {
  1105. $hash->{STATE} = "Error";
  1106. $hash->{ConnectionState} = "Error";
  1107. RemoveInternalTimer($hash);
  1108. InternalTimer(gettimeofday()+60, "harmony_connect", $hash, 0);
  1109. }
  1110. }
  1111. sub
  1112. harmony_data2string($)
  1113. {
  1114. my ($data) = @_;
  1115. return "" if( !defined($data) );
  1116. return $data if( !ref($data) );
  1117. return $data if( ref($data) =~ m/JSON::..::Boolean/ );
  1118. return "[". join(',', @{$data}) ."]" if(ref($data) eq "ARRAY");
  1119. return Dumper $data;
  1120. }
  1121. sub
  1122. harmony_GetPower($$)
  1123. {
  1124. my ($hash, $activity) = @_;
  1125. my $power = "";
  1126. return $power if( !defined($activity->{fixit}) );
  1127. foreach my $id (keys %{$activity->{fixit}}) {
  1128. my $label = harmony_labelOfDevice($hash, $id);
  1129. my $state = $activity->{fixit}->{$id}->{Power};
  1130. $state = "Manual" if( !$state );
  1131. $power .= "\n\t\t\t$label: $state";
  1132. }
  1133. return $power;
  1134. }
  1135. sub
  1136. harmony_Get($$@)
  1137. {
  1138. my ($hash, $name, $cmd, @params) = @_;
  1139. my ($param) = @params;
  1140. #$cmd = lc( $cmd );
  1141. my $list = "";
  1142. if( defined($hash->{id}) ) {
  1143. if( !$hash->{hub} ) {
  1144. $hash->{hub} = harmony_hubOfDevice($hash->{id});
  1145. return "no IODev found for device $name ($hash->{id})" if( !$hash->{hub} );
  1146. }
  1147. if( $cmd eq "commands" || $cmd eq "deviceCommands" ) {
  1148. $cmd = "deviceCommands";
  1149. $param = $hash->{id};
  1150. $hash = $defs{$hash->{hub}};
  1151. } else {
  1152. $list = "commands:noArg";
  1153. return "Unknown argument $cmd, choose one of $list";
  1154. }
  1155. }
  1156. my $ret;
  1157. if( $cmd eq "activities" ) {
  1158. return "no activities found" if( !defined($hash->{config}) || !defined($hash->{config}->{activity}) );
  1159. my $ret = "";
  1160. foreach my $activity (sort { ($a->{activityOrder}||0) <=> ($b->{activityOrder}||0) } @{$hash->{config}->{activity}}) {
  1161. next if( $activity->{id} == -1 );
  1162. $ret .= "\n" if( $ret );
  1163. $ret .= sprintf( "%s\t%-24s", $activity->{id}, $activity->{label});
  1164. foreach my $param (@params) {
  1165. $ret .= "\t". harmony_data2string($activity->{$param}) if( $param && defined($activity->{$param}) );
  1166. }
  1167. if( $param && $param eq "power" ) {
  1168. my $power = harmony_GetPower($hash, $activity);
  1169. $ret .= $power if( $power );
  1170. }
  1171. }
  1172. if( my $activity = harmony_activityOfId($hash, -1) ) {
  1173. $ret .= "\n-1\t\t$activity->{label}";
  1174. if( $param && $param eq "power" ) {
  1175. my $power = harmony_GetPower($hash, $activity);
  1176. $ret .= $power if( $power );
  1177. }
  1178. }
  1179. #$ret = sprintf("%s\t\t%-24s\n", "ID", "LABEL"). $ret if( $ret );
  1180. return $ret;
  1181. } elsif( $cmd eq "devices" ) {
  1182. return "no devices found" if( !defined($hash->{config}) || !defined($hash->{config}->{device}) );
  1183. my $ret = "";
  1184. foreach my $device (sort { $a->{id} <=> $b->{id} } @{$hash->{config}->{device}}) {
  1185. $ret .= "\n" if( $ret );
  1186. $ret .= sprintf( "%s\t%-20s\t%-20s\t%-15s\t%-15s", $device->{id}, $device->{label}, $device->{type}, $device->{manufacturer}, $device->{model});
  1187. foreach my $param (@params) {
  1188. $ret .= "\t". harmony_data2string($device->{$param}) if( $param && defined($device->{$param}) );
  1189. }
  1190. }
  1191. #$ret = sprintf("%s\t\t%-20s\t%-20s\t%-15s\t%-15s\n", "ID", "LABEL", "TYPE", "MANUFACTURER", "MODEL"). $ret if( $ret );
  1192. return $ret;
  1193. } elsif( $cmd eq "commands" ) {
  1194. return "no commands found" if( !defined($hash->{config}) || !defined($hash->{config}->{activity}) );
  1195. my $id = $param;
  1196. $id = harmony_idOfActivity($hash, $id) if( $id && $id !~ m/^([\d-])+$/ );
  1197. return "unknown activity $param" if( $param && !$id );
  1198. my $ret = "";
  1199. foreach my $activity (sort { ($a->{activityOrder}||0) <=> ($b->{activityOrder}||0) } @{$hash->{config}->{activity}}) {
  1200. next if( $activity->{id} == -1 );
  1201. next if( $id && $activity->{id} != $id );
  1202. $ret .= "$activity->{label}\n";
  1203. #$ret .= "$device->{label}\t$device->{manufacturer}\t$device->{model}\n";
  1204. foreach my $group (@{$activity->{controlGroup}}) {
  1205. $ret .= "\t$group->{name}\n";
  1206. foreach my $function (@{$group->{function}}) {
  1207. my $action = harmony_decode_json($function->{action});
  1208. $ret .= sprintf( "\t\t%-20s\t%s (%s)\n", $function->{name}, $function->{label}, harmony_labelOfDevice($hash, $action->{deviceId}, $action->{deviceId}) );
  1209. }
  1210. }
  1211. }
  1212. return $ret;
  1213. } elsif( $cmd eq "deviceCommands" ) {
  1214. return "no commands found" if( !defined($hash->{config}) || !defined($hash->{config}->{device}) );
  1215. my $id = $param;
  1216. $id = harmony_idOfDevice($hash, $id) if( $id && $id !~ m/^([\d-])+$/ );
  1217. return "unknown device $param" if( $param && !$id );
  1218. my $ret = "";
  1219. foreach my $device (sort { $a->{id} <=> $b->{id} } @{$hash->{config}->{device}}) {
  1220. next if( $id && $device->{id} != $id );
  1221. $ret .= "$device->{label}\t$device->{manufacturer}\t$device->{model}\n";
  1222. foreach my $group (@{$device->{controlGroup}}) {
  1223. $ret .= "\t$group->{name}\n";
  1224. foreach my $function (@{$group->{function}}) {
  1225. $ret .= sprintf( "\t\t%-20s\t%s\n", $function->{name}, $function->{label} );
  1226. }
  1227. }
  1228. }
  1229. return "no commands found" if( !$ret );
  1230. return $ret;
  1231. } elsif( $cmd eq "activityDetail"
  1232. || $cmd eq "deviceDetail" ) {
  1233. return undef if( !defined($hash->{config}) );
  1234. $param = harmony_idOfActivity($hash, $param) if( $param && $param !~ m/^([\d-])+$/ && $cmd eq "activityDetail" );
  1235. $param = harmony_idOfDevice($hash, $param) if( $param && $param !~ m/^([\d-])+$/ && $cmd eq "deviceDetail" );
  1236. my $var;
  1237. $var = $hash->{config}->{activity} if( $cmd eq "activityDetail" );
  1238. $var = $hash->{config}->{device} if( $cmd eq "deviceDetail" );
  1239. if( $param ) {
  1240. foreach my $v (@{$var}) {
  1241. if( $v->{id} eq $param ) {
  1242. $var = $v;
  1243. last;
  1244. }
  1245. }
  1246. }
  1247. return Dumper $var;
  1248. } elsif( $cmd eq "configDetail" ) {
  1249. return undef if( !defined($hash->{config}) );
  1250. return Dumper $hash->{config};
  1251. } elsif( $cmd eq "currentActivity" ) {
  1252. return "unknown activity" if( !$hash->{currentActivityID} );
  1253. my $activity = harmony_activityOfId($hash, $hash->{currentActivityID});
  1254. return "unknown activity" if( !$activity );
  1255. return $activity->{label};
  1256. }
  1257. $list .= "activities:noArg devices:noArg";
  1258. if( $hash->{config} ) {
  1259. return undef if( !defined($hash->{config}) );
  1260. my $activities;
  1261. foreach my $activity (sort { ($a->{activityOrder}||0) <=> ($b->{activityOrder}||0) } @{$hash->{config}->{activity}}) {
  1262. next if( $activity->{id} == -1 );
  1263. $activities .= "," if( $activities );
  1264. $activities .= $activity->{label};
  1265. }
  1266. if( $activities ) {
  1267. $activities =~ s/ /./g;
  1268. $list .= " commands:,$activities";
  1269. }
  1270. my $devices;
  1271. foreach my $device (sort { $a->{id} <=> $b->{id} } @{$hash->{config}->{device}}) {
  1272. $devices .= "," if( $devices );
  1273. $devices .= $device->{label};
  1274. }
  1275. if( $devices ) {
  1276. $devices =~ s/ /./g;
  1277. $list .= " deviceCommands:,$devices";
  1278. }
  1279. }
  1280. if( $cmd eq 'showAccount' ) {
  1281. my $user = $hash->{helper}{username};
  1282. my $password = $hash->{helper}{password};
  1283. return 'no user set' if( !$user );
  1284. return 'no password set' if( !$password );
  1285. $user = harmony_decrypt( $user );
  1286. $password = harmony_decrypt( $password );
  1287. return "user: $user\npassword: $password";
  1288. }
  1289. $list .= " showAccount";
  1290. $list .= " currentActivity:noArg";
  1291. $list =~ s/^ //;
  1292. return "Unknown argument $cmd, choose one of $list";
  1293. }
  1294. sub
  1295. harmony_Attr($$$)
  1296. {
  1297. my ($cmd, $name, $attrName, $attrVal) = @_;
  1298. my $orig = $attrVal;
  1299. if( $attrName eq "disable" ) {
  1300. my $hash = $defs{$name};
  1301. RemoveInternalTimer($hash);
  1302. if( $cmd eq "set" && $attrVal ne "0" ) {
  1303. $attrVal = 1;
  1304. harmony_disconnect($hash);
  1305. } else {
  1306. $attr{$name}{$attrName} = 0;
  1307. harmony_connect($hash);
  1308. }
  1309. }
  1310. if( $cmd eq "set" ) {
  1311. if( !defined($orig) || $orig ne $attrVal ) {
  1312. $attr{$name}{$attrName} = $attrVal;
  1313. return $attrName ." set to ". $attrVal;
  1314. }
  1315. }
  1316. return;
  1317. }
  1318. sub
  1319. harmony_encrypt($)
  1320. {
  1321. my ($decoded) = @_;
  1322. my $key = getUniqueId();
  1323. my $encoded;
  1324. return $decoded if( $decoded =~ /^crypt:(.*)/ );
  1325. for my $char (split //, $decoded) {
  1326. my $encode = chop($key);
  1327. $encoded .= sprintf("%.2x",ord($char)^ord($encode));
  1328. $key = $encode.$key;
  1329. }
  1330. return 'crypt:'. $encoded;
  1331. }
  1332. sub
  1333. harmony_decrypt($)
  1334. {
  1335. my ($encoded) = @_;
  1336. my $key = getUniqueId();
  1337. my $decoded;
  1338. $encoded = $1 if( $encoded =~ /^crypt:(.*)/ );
  1339. for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) {
  1340. my $decode = chop($key);
  1341. $decoded .= chr(ord($char)^ord($decode));
  1342. $key = $decode.$key;
  1343. }
  1344. return $decoded;
  1345. }
  1346. 1;
  1347. =pod
  1348. =item summary module for logitech harmony hub based remots
  1349. =item summary_DE Modul für Logitech Harmony Hub basierte Fernbedienungen
  1350. =begin html
  1351. <a name="harmony"></a>
  1352. <h3>harmony</h3>
  1353. <ul>
  1354. Defines a device to integrate a Logitech Harmony Hub based remote control into fhem.<br><br>
  1355. It is possible to: start and stop activities, send ir commands to devices, send keyboard input by bluetooth and
  1356. smart keyboard usb dongles.<br><br>
  1357. You probably want to use it in conjunction with the <a href="#fakeRoku">fakeRoku</a> module.<br><br>
  1358. Notes:
  1359. <ul>
  1360. <li>JSON has to be installed on the FHEM host.</li>
  1361. <li>For hubs with firmware version 3.x.y &lt;username&gt; and &lt;password&gt; are not required as no authentication
  1362. with the logitech myharmony server is needed for the full functionality of this module.</li>
  1363. <li>For hubs with firmware version 4.x.y &lt;username&gt; and &lt;password&gt; are required for device level control.
  1364. Activit level control is (currently) still possible without authentication.</li>
  1365. <li>activity and device names can be given as id or name. names can be given as a regex and spaces in names musst be replaced by a single '.' (dot).</li>
  1366. </ul><br>
  1367. <a name="harmony_Define"></a>
  1368. <b>Define</b>
  1369. <ul>
  1370. <code>define &lt;name&gt; harmony [&lt;username&gt; &lt;password&gt;] &lt;ip&gt;</code><br>
  1371. <br>
  1372. Defines a harmony device.<br><br>
  1373. Examples:
  1374. <ul>
  1375. <code>define hub harmony 10.0.1.4</code><br>
  1376. </ul>
  1377. </ul><br>
  1378. <a name="harmony_Readings"></a>
  1379. <b>Readings</b>
  1380. <ul>
  1381. <li>currentActivity<br>
  1382. the name of the currently selected activity.</li>
  1383. <li>previousActivity<br>
  1384. the name of the previous selected activity. does not trigger an event.</li>
  1385. <li>newVersion<br>
  1386. will be set if a new firmware version is avaliable.</li>
  1387. </ul><br>
  1388. <a name="harmony_Internals"></a>
  1389. <b>Internals</b>
  1390. <ul>
  1391. <li>currentActivityID<br>
  1392. the id of the currently selected activity.</li>
  1393. <li>previousActivityID<br>
  1394. the id of the previous selected activity.</li>
  1395. <li>sleeptimer<br>
  1396. timeout for sleeptimer if any is set.</li>
  1397. </ul><br>
  1398. <a name="harmony_Set"></a>
  1399. <b>Set</b>
  1400. <ul>
  1401. <li>activity &lt;id&gt;|&lt;name&gt; [&lt;channel&gt;]<br>
  1402. switch to this activit and optionally switch to &lt;channel&gt;</li>
  1403. <li>channel &lt;channel&gt;<br>
  1404. switch to &lt;channel&gt; in the current activity</li>
  1405. <li>command [&lt;id&gt;|&lt;name&gt;] &lt;command&gt; [duration=&lt;duration&gt;]<br>
  1406. send the given ir command for the current activity or for the given device</li>
  1407. <li>getConfig<br>
  1408. request the configuration from the hub</li>
  1409. <li>getCurrentActivity<br>
  1410. request the current activity from the hub</li>
  1411. <li>off<br>
  1412. switch current activity off</li>
  1413. <li>reconnect [all]<br>
  1414. close connection to the hub and reconnect, if <code>all</code> is given also reconnect to the logitech server</li>
  1415. <li>sleeptimer [&lt;timeout&gt;]<br>
  1416. &lt;timeout&gt; -> timeout in minutes<br>
  1417. -1 -> timer off<br>
  1418. default -> 60 minutes</li>
  1419. <li>sync<br>
  1420. syncs the hub to the myHarmony config</li>
  1421. <li>hidDevice [&lt;id&gt;|&lt;name&gt;]<br>
  1422. sets the target device for keyboard commands, if no device is given -> set the target to the
  1423. default device for the current activity.</li>
  1424. <li>text &lt;text&gt;<br>
  1425. sends &lt;text&gt; by bluetooth/smart keaboard dongle. a-z ,A-Z ,0-9, \n, \e, \t and space are currently possible</li>
  1426. <li>cursor &lt;direction&gt;<br>
  1427. moves the cursor by bluetooth/smart keaboard dongle. &lt;direction&gt; can be one of: up, down, left, right, pageUp, pageDown, home, end.</li>
  1428. <li>special &lt;key&gt;<br>
  1429. sends special key by bluetooth/smart keaboard dongle. &lt;key&gt; can be one of: previousTrack, nextTrack, stop, playPause, volumeUp, volumeDown, mute.</li>
  1430. <li>autocreate [&lt;id&gt;|&lt;name&gt;]<br>
  1431. creates a fhem device for a single/all device(s) in the harmony hub. if activities are startet the state
  1432. of these devices will be updatet with the power state defined in these activites.</li>
  1433. <li>update<br>
  1434. triggers a firmware update. only available if a new firmware is available.</li>
  1435. <li>inactive<br>
  1436. inactivates the current device. note the slight difference to the
  1437. disable attribute: using set inactive the state is automatically saved
  1438. to the statefile on shutdown, there is no explicit save necesary.<br>
  1439. this command is intended to be used by scripts to temporarily
  1440. deactivate the harmony device.<br>
  1441. the concurrent setting of the disable attribute is not recommended.</li>
  1442. <li>active<br>
  1443. activates the current device (see inactive).</li>
  1444. </ul>
  1445. The command, hidDevice, text, cursor and special commmands are also available for the autocreated devices. The &lt;id&gt;|&lt;name&gt; paramter hast to be omitted.<br><br>
  1446. <a name="harmony_Get"></a>
  1447. <b>Get</b>
  1448. <ul>
  1449. <li>activites [&lt;param&gt;]<br>
  1450. lists all activities<br>
  1451. parm = power -> list power state for each device in activity</li>
  1452. <li>devices [&lt;param&gt;]<br>
  1453. lists all devices</li>
  1454. <li>commands [&lt;id&gt;|&lt;name&gt;]<br>
  1455. lists the commands for the specified activity or for all activities</li>
  1456. <li>deviceCommands [&lt;id&gt;|&lt;name&gt;]<br>
  1457. lists the commands for the specified device or for all devices</li>
  1458. <li>activityDetail [&lt;id&gt;|&lt;name&gt;]</li>
  1459. <li>deviceDetail [&lt;id&gt;|&lt;name&gt;]</li>
  1460. <li>configDetail</li>
  1461. <li>currentActivity<br>
  1462. returns the current activity name</li>
  1463. <li>showAccount<br>
  1464. display obfuscated user and password in cleartext</li>
  1465. </ul>
  1466. The commands commmand is also available for the autocreated devices. The &lt;id&gt;|&lt;name&gt; paramter hast to be omitted.<br><br>
  1467. <a name="harmony_Attr"></a>
  1468. <b>Attributes</b>
  1469. <ul>
  1470. <li>disable<br>
  1471. 1 -> disconnect from the hub</li>
  1472. </ul>
  1473. </ul>
  1474. =end html
  1475. =cut