70_XBMC.pm 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707
  1. ##############################################
  2. #
  3. # A module to control XBMC and receive events from XBMC.
  4. # Requires XBMC "Frodo" 12.0.
  5. # To use this module you will have to enable JSON-RPC. See http://wiki.xbmc.org/index.php?title=JSON-RPC_API#Enabling_JSON-RPC
  6. # Also the Perl module JSON is required.
  7. #
  8. # written 2013 by Dennis Bokermann <dbn at gmx.de>
  9. #
  10. ##############################################
  11. # $Id: 70_XBMC.pm 12906 2016-12-29 22:55:17Z vbs $
  12. package main;
  13. use strict;
  14. use warnings;
  15. use POSIX;
  16. use JSON;
  17. use Data::Dumper;
  18. use DevIo;
  19. use IO::Socket::INET;
  20. use MIME::Base64;
  21. sub XBMC_Initialize($$)
  22. {
  23. my ($hash) = @_;
  24. $hash->{DefFn} = "XBMC_Define";
  25. $hash->{SetFn} = "XBMC_Set";
  26. $hash->{ReadFn} = "XBMC_Read";
  27. $hash->{ReadyFn} = "XBMC_Ready";
  28. $hash->{UndefFn} = "XBMC_Undefine";
  29. $hash->{AttrFn} = "XBMC_Attr";
  30. $hash->{AttrList} = "fork:enable,disable compatibilityMode:xbmc,plex offMode:quit,hibernate,shutdown,suspend updateInterval disable:1,0 " . $readingFnAttributes;
  31. $data{RC_makenotify}{XBMC} = "XBMC_RCmakenotify";
  32. $data{RC_layout}{XBMC_RClayout} = "XBMC_RClayout";
  33. }
  34. sub XBMC_Define($$)
  35. {
  36. my ($hash, $def) = @_;
  37. DevIo_CloseDev($hash);
  38. my @args = split("[ \t]+", $def);
  39. if (int(@args) < 3) {
  40. return "Invalid number of arguments: define <name> XBMC <ip[:port]> <http|tcp> [<username>] [<password>]";
  41. }
  42. my ($name, $type, $addr, $protocol, $username, $password) = @args;
  43. $hash->{Protocol} = $protocol;
  44. $hash->{NextID} = 1;
  45. $addr =~ /^(.*?)(:([0-9]+))?$/;
  46. $hash->{Host} = $1;
  47. if(defined($3)) {
  48. $hash->{Port} = $3;
  49. }
  50. elsif($protocol eq 'tcp') {
  51. $hash->{Port} = 9090; #Default TCP Port
  52. }
  53. else {
  54. $hash->{Port} = 80;
  55. }
  56. $hash->{STATE} = 'Initialized';
  57. if($protocol eq 'tcp') {
  58. $hash->{DeviceName} = $hash->{Host} . ":" . $hash->{Port};
  59. my $dev = $hash->{DeviceName};
  60. $readyfnlist{"$name.$dev"} = $hash;
  61. }
  62. elsif(defined($username) && defined($password)) {
  63. $hash->{Username} = $username;
  64. $hash->{Password} = $password;
  65. }
  66. else {
  67. return "Username and/or password missing.";
  68. }
  69. $attr{$hash->{NAME}}{"updateInterval"} = 60;
  70. return undef;
  71. }
  72. sub XBMC_Attr($$$$)
  73. {
  74. my ($cmd, $name, $attr, $value) = @_;
  75. my $hash = $defs{$name};
  76. if($attr eq "disable") {
  77. if($cmd eq "set" && ($value || !defined($value))) {
  78. XBMC_Disconnect($hash);
  79. $hash->{STATE} = "Disabled";
  80. } else {
  81. if (AttrVal($hash->{NAME}, 'disable', 0)) {
  82. $hash->{STATE} = "Initialized";
  83. my $dev = $hash->{DeviceName};
  84. $readyfnlist{"$name.$dev"} = $hash;
  85. }
  86. }
  87. }
  88. return undef;
  89. }
  90. sub XBMC_CreateId($)
  91. {
  92. my ($hash) = @_;
  93. my $res = $hash->{NextID};
  94. $hash->{NextID} = ($res >= 1000000) ? 1 : $res + 1;
  95. return $res;
  96. }
  97. # Force a connection attempt to XBMC as soon as possible
  98. # (e.g. you know you just started it and want to connect immediately without waiting up to 60 s)
  99. sub XBMC_Connect($)
  100. {
  101. my ($hash) = @_;
  102. my $name = $hash->{NAME};
  103. if($hash->{Protocol} ne 'tcp') {
  104. # we dont have a persistent connection anyway
  105. return undef;
  106. }
  107. if(AttrVal($hash->{NAME},'fork','disable') eq 'enable') {
  108. return undef unless $hash->{CHILDPID}; # nothing to do
  109. # well, the fork process does not respond to SIGTERM
  110. # so lets use SIGKILL to make things clear to it
  111. if ((kill SIGKILL, $hash->{CHILDPID}) != 1) {
  112. Log3 3, $name, "XBMC_Connect: ERROR: Unable to kill fork process!";
  113. return undef;
  114. }
  115. $hash->{CHILDPID} = undef; # undefg childpid so the Ready-func will fork again
  116. } else {
  117. $hash->{NEXT_OPEN} = 0; # force NEXT_OPEN used in DevIO
  118. }
  119. return undef;
  120. }
  121. # kills child process trying to connect (if existing)
  122. sub XBMC_KillConnectionChild($)
  123. {
  124. my ($hash) = @_;
  125. return if !$hash->{CHILDPID};
  126. kill 'KILL', $hash->{CHILDPID};
  127. undef $hash->{CHILDPID};
  128. }
  129. sub XBMC_Ready($)
  130. {
  131. my ($hash) = @_;
  132. if (AttrVal($hash->{NAME}, 'disable', 0)) {
  133. return;
  134. }
  135. if($hash->{Protocol} eq 'tcp') {
  136. if(AttrVal($hash->{NAME},'fork','disable') eq 'enable') {
  137. if($hash->{CHILDPID} && !(kill 0, $hash->{CHILDPID})) {
  138. $hash->{CHILDPID} = undef;
  139. return DevIo_OpenDev($hash, 1, "XBMC_Init");
  140. }
  141. elsif(!$hash->{CHILDPID}) {
  142. return if($hash->{CHILDPID} = fork);
  143. my $ppid = getppid();
  144. ### Copied from Blocking.pm
  145. foreach my $d (sort keys %defs) { # Close all kind of FD
  146. my $h = $defs{$d};
  147. #the following line was added by vbs to not close parent's DbLog DB handle
  148. $h->{DBH}->{InactiveDestroy} = 1 if ($h->{TYPE} eq 'DbLog');
  149. TcpServer_Close($h) if($h->{SERVERSOCKET});
  150. if($h->{DeviceName}) {
  151. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  152. DevIo_CloseDev($h,1);
  153. }
  154. }
  155. ### End of copied from Blocking.pm
  156. while(kill 0, $ppid) {
  157. DevIo_OpenDev($hash, 1, "XBMC_ChildExit");
  158. sleep(5);
  159. }
  160. exit(0);
  161. }
  162. } else {
  163. return DevIo_OpenDev($hash, 1, "XBMC_Init");
  164. }
  165. }
  166. return undef;
  167. }
  168. sub XBMC_ChildExit($)
  169. {
  170. exit(0);
  171. }
  172. sub XBMC_Undefine($$)
  173. {
  174. my ($hash,$arg) = @_;
  175. RemoveInternalTimer($hash);
  176. XBMC_Disconnect($hash);
  177. return undef;
  178. }
  179. sub XBMC_Disconnect($)
  180. {
  181. my ($hash) = @_;
  182. if($hash->{Protocol} eq 'tcp') {
  183. DevIo_CloseDev($hash);
  184. }
  185. XBMC_KillConnectionChild($hash);
  186. }
  187. sub XBMC_Init($)
  188. {
  189. my ($hash) = @_;
  190. XBMC_ResetPlayerReadings($hash);
  191. #since we just successfully connected to XBMC I guess its safe to assume the device is awake
  192. readingsSingleUpdate($hash,"system","wake",1);
  193. $hash->{LAST_RECV} = time();
  194. XBMC_Update($hash);
  195. XBMC_QueueIntervalUpdate($hash);
  196. return undef;
  197. }
  198. sub XBMC_QueueIntervalUpdate($;$) {
  199. my ($hash, $time) = @_;
  200. # AFAIK when using http this module is not using a persistent TCP connection
  201. return if(($hash->{Protocol} eq 'http') || ($hash->{STATE} eq "disconnected"));
  202. if (!defined($time)) {
  203. $time = AttrVal($hash->{NAME},'updateInterval',60);
  204. }
  205. RemoveInternalTimer($hash);
  206. InternalTimer(time() + $time, "XBMC_Check", $hash, 0);
  207. }
  208. sub XBMC_Check($) {
  209. my ($hash) = @_;
  210. my $name = $hash->{NAME};
  211. Log3 $name, 4, "XBMC_Check";
  212. return if(!XBMC_CheckConnection($hash));
  213. XBMC_Update($hash);
  214. #xbmc seems alive if we get here. so keep bugging it
  215. XBMC_QueueIntervalUpdate($hash);
  216. }
  217. sub XBMC_UpdatePlayerItem($) {
  218. my ($hash) = @_;
  219. my $name = $hash->{NAME};
  220. Log3 $name, 4, "XBMC_UpdatePlayerItem";
  221. if (($hash->{STATE} eq 'disconnected') or (ReadingsVal($name, "playStatus","") ne 'playing')) {
  222. Log3 $name, 4, "XBMC_UpdatePlayerItem - cancelled (disconnected or not playing)";
  223. return;
  224. }
  225. XBMC_PlayerGetItem($hash, -1);
  226. }
  227. sub XBMC_CheckConnection($) {
  228. my ($hash) = @_;
  229. my $name = $hash->{NAME};
  230. if ($hash->{STATE} eq "disconnected") {
  231. # we are already disconnected
  232. return 0;
  233. }
  234. my $lastRecvDiff = (time() - $hash->{LAST_RECV});
  235. my $updateInt = AttrVal($hash->{NAME},'updateInterval',60);
  236. # give it 50% tolerance. sticking hard to updateInt might fail if the fhem timer gets delayed for some seconds
  237. if ($lastRecvDiff > ($updateInt * 1.5)) {
  238. Log3 $name, 3, "XBMC_CheckConnection: Connection lost! Last data from Kodi received $lastRecvDiff s ago";
  239. DevIo_Disconnected($hash);
  240. return 0;
  241. }
  242. Log3 $name, 4, "XBMC_CheckConnection: Connection still alive. Last data from Kodi received $lastRecvDiff s ago";
  243. return 1;
  244. }
  245. sub XBMC_Update($)
  246. {
  247. my ($hash) = @_;
  248. my $obj;
  249. $obj = {
  250. "method" => "Application.GetProperties",
  251. "params" => {
  252. "properties" => ["volume","muted","name","version"]
  253. }
  254. };
  255. XBMC_Call($hash,$obj,1);
  256. $obj = {
  257. "method" => "GUI.GetProperties",
  258. "params" => {
  259. "properties" => ["skin","fullscreen", "stereoscopicmode"]
  260. }
  261. };
  262. XBMC_Call($hash,$obj,1);
  263. # the playerId in the message is not reliable
  264. # xbmc is not able to assign the correct player so the playerid might be wrong
  265. # http://forum.kodi.tv/showthread.php?tid=174872
  266. # so we ask for the acutally running players by passing -1
  267. XBMC_PlayerUpdate($hash, -1);
  268. XBMC_UpdatePlayerItem($hash);
  269. }
  270. sub XBMC_PlayerUpdate($$)
  271. {
  272. my $hash = shift;
  273. my $playerid = shift;
  274. my $obj = {
  275. "method" => "Player.GetProperties",
  276. "params" => {
  277. "properties" => ["time","totaltime", "repeat", "shuffled", "speed" ]
  278. #"canseek", "canchangespeed", "canmove", "canzoom", "canrotate", "canshuffle", "canrepeat"
  279. }
  280. };
  281. push(@{$obj->{params}->{properties}}, 'partymode') if(AttrVal($hash->{NAME},'compatibilityMode','xbmc') eq 'xbmc');
  282. if($playerid >= 0) {
  283. $obj->{params}->{playerid} = $playerid;
  284. XBMC_Call($hash,$obj,1);
  285. }
  286. else {
  287. XBMC_PlayerCommand($hash,$obj,0);
  288. }
  289. }
  290. sub XBMC_PlayerGetItem($$)
  291. {
  292. my $hash = shift;
  293. my $playerid = shift;
  294. my $obj = {
  295. "method" => "Player.GetItem",
  296. "params" => {
  297. "properties" => ["artist", "album", "thumbnail", "file", "title",
  298. "track", "year", "streamdetails", "tvshowid"]
  299. }
  300. };
  301. if($playerid >= 0) {
  302. $obj->{params}->{playerid} = $playerid;
  303. XBMC_Call($hash,$obj,1);
  304. }
  305. else {
  306. XBMC_PlayerCommand($hash,$obj,0);
  307. }
  308. }
  309. sub XBMC_Read($)
  310. {
  311. my ($hash) = @_;
  312. my $buffer = DevIo_SimpleRead($hash);
  313. return if (not defined($buffer));
  314. return XBMC_ProcessRead($hash, $buffer);
  315. }
  316. sub XBMC_ProcessRead($$)
  317. {
  318. my ($hash, $data) = @_;
  319. my $name = $hash->{NAME};
  320. my $buffer = '';
  321. Log3($name, 5, "XBMC_ProcessRead");
  322. #include previous partial message
  323. if(defined($hash->{PARTIAL}) && $hash->{PARTIAL}) {
  324. Log3($name, 5, "XBMC_Read: PARTIAL: " . $hash->{PARTIAL});
  325. $buffer = $hash->{PARTIAL};
  326. }
  327. else {
  328. Log3($name, 5, "No PARTIAL buffer");
  329. }
  330. Log3($name, 5, "XBMC_Read: Incoming data: " . $data);
  331. $buffer = $buffer . $data;
  332. Log3($name, 5, "XBMC_Read: Current processing buffer (PARTIAL + incoming data): " . $buffer);
  333. my ($msg,$tail) = XBMC_ParseMsg($hash, $buffer);
  334. #processes all complete messages
  335. while($msg) {
  336. $hash->{LAST_RECV} = time();
  337. Log3($name, 4, "XBMC_Read: Decoding JSON message. Length: " . length($msg) . " Content: " . $msg);
  338. my $obj = JSON->new->utf8(0)->decode($msg);
  339. #it is a notification if a method name is present
  340. if(defined($obj->{method})) {
  341. XBMC_ProcessNotification($hash,$obj);
  342. }
  343. elsif(defined($obj->{error})) {
  344. Log3($name, 3, "XBMC_Read: Received error message: " . $msg);
  345. }
  346. #otherwise it is a answer of a request
  347. else {
  348. if (XBMC_ProcessResponse($hash,$obj) == -1) {
  349. Log3($name, 2, "XBMC_ProcessRead: Faulty message: $msg");
  350. }
  351. }
  352. ($msg,$tail) = XBMC_ParseMsg($hash, $tail);
  353. }
  354. $hash->{PARTIAL} = $tail;
  355. Log3($name, 5, "XBMC_Read: Tail: " . $tail);
  356. Log3($name, 5, "XBMC_Read: PARTIAL: " . $hash->{PARTIAL});
  357. return;
  358. }
  359. sub XBMC_ResetMediaReadings($)
  360. {
  361. my ($hash) = @_;
  362. readingsBeginUpdate($hash);
  363. readingsBulkUpdate($hash, "currentMedia", "" );
  364. readingsBulkUpdate($hash, "currentOriginaltitle", "" );
  365. readingsBulkUpdate($hash, "currentShowtitle", "" );
  366. readingsBulkUpdate($hash, "currentTitle", "" );
  367. readingsBulkUpdate($hash, "episode", "" );
  368. readingsBulkUpdate($hash, "episodeid", "" );
  369. readingsBulkUpdate($hash, "season", "" );
  370. readingsBulkUpdate($hash, "label", "" );
  371. readingsBulkUpdate($hash, "movieid", "" );
  372. readingsBulkUpdate($hash, "playlist", "" );
  373. readingsBulkUpdate($hash, "type", "" );
  374. readingsBulkUpdate($hash, "year", "" );
  375. readingsBulkUpdate($hash, "3dfile", "" );
  376. readingsBulkUpdate($hash, "currentAlbum", "" );
  377. readingsBulkUpdate($hash, "currentArtist", "" );
  378. readingsBulkUpdate($hash, "songid", "" );
  379. readingsBulkUpdate($hash, "currentTrack", "" );
  380. readingsEndUpdate($hash, 1);
  381. # delete streamdetails readings
  382. # NOTE: we actually delete the readings (unlike the other readings)
  383. # because they are stream count dependent
  384. fhem("deletereading $hash->{NAME} sd_.*", 1);
  385. }
  386. sub XBMC_ResetPlayerReadings($)
  387. {
  388. my ($hash) = @_;
  389. readingsBeginUpdate($hash);
  390. readingsBulkUpdate($hash, "time", "" );
  391. readingsBulkUpdate($hash, "totaltime", "" );
  392. readingsBulkUpdate($hash, "shuffle", "" );
  393. readingsBulkUpdate($hash, "repeat", "" );
  394. readingsBulkUpdate($hash, "speed", "" );
  395. readingsBulkUpdate($hash, "partymode", "" );
  396. readingsBulkUpdate($hash, "playStatus", "stopped" );
  397. readingsEndUpdate($hash, 1);
  398. }
  399. sub XBMC_PlayerOnPlay($$)
  400. {
  401. my ($hash,$obj) = @_;
  402. my $name = $hash->{NAME};
  403. my $id = XBMC_CreateId($hash);
  404. my $type = $obj->{params}->{data}->{item}->{type};
  405. if(AttrVal($hash->{NAME},'compatibilityMode','xbmc') eq 'plex' || !defined($obj->{params}->{data}->{item}->{id}) || $type eq "picture" || $type eq "unknown") {
  406. # we either got unknown or picture OR an item not in the library (id not existing)
  407. readingsBeginUpdate($hash);
  408. readingsBulkUpdate($hash,'playStatus','playing');
  409. readingsBulkUpdate($hash,'type',$type);
  410. if(defined($obj->{params}->{data}->{item})) {
  411. foreach my $key (keys %{$obj->{params}->{data}->{item}}) {
  412. my $value = $obj->{params}->{data}->{item}->{$key};
  413. XBMC_CreateReading($hash,$key,$value);
  414. }
  415. }
  416. readingsEndUpdate($hash, 1);
  417. XBMC_PlayerGetItem($hash, -1);
  418. }
  419. elsif($type eq "song") {
  420. #
  421. my $req = {
  422. "method" => "AudioLibrary.GetSongDetails",
  423. "params" => {
  424. "songid" => $obj->{params}->{data}->{item}->{id},
  425. "properties" => ["artist","album","title","track","file"]
  426. },
  427. "id" => $id
  428. };
  429. my $event = {
  430. "name" => $obj->{method},
  431. "type" => "song",
  432. "event" => 'Player.OnPlay'
  433. };
  434. $hash->{PendingEvents}{$id} = $event;
  435. XBMC_Call($hash, $req,1);
  436. }
  437. elsif($type eq "episode") {
  438. my $req = {
  439. "method" => "VideoLibrary.GetEpisodeDetails",
  440. "params" => {
  441. "episodeid" => $obj->{params}->{data}->{item}->{id},
  442. #http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Video.Fields.Episode
  443. "properties" => ["season","episode","title","showtitle","file"]
  444. },
  445. "id" => $id
  446. };
  447. my $event = {
  448. "name" => $obj->{method},
  449. "type" => "episode",
  450. "event" => 'Player.OnPlay'
  451. };
  452. $hash->{PendingEvents}{$id} = $event;
  453. XBMC_Call($hash, $req,1);
  454. }
  455. elsif($type eq "movie") {
  456. my $req = {
  457. "method" => "VideoLibrary.GetMovieDetails",
  458. "params" => {
  459. "movieid" => $obj->{params}->{data}->{item}->{id},
  460. #http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Video.Fields.Movie
  461. "properties" => ["title","file","year","originaltitle","streamdetails"]
  462. },
  463. "id" => $id
  464. };
  465. my $event = {
  466. "name" => $obj->{method},
  467. "type" => "movie",
  468. "event" => 'Player.OnPlay'
  469. };
  470. $hash->{PendingEvents}{$id} = $event;
  471. XBMC_Call($hash, $req,1);
  472. }
  473. elsif($type eq "musicvideo") {
  474. my $req = {
  475. "method" => "VideoLibrary.GetMusicVideoDetails",
  476. "params" => {
  477. "musicvideoid" => $obj->{params}->{data}->{item}->{id},
  478. #http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Video.Fields.MusicVideo
  479. "properties" => ["title","artist","album","file"]
  480. },
  481. "id" => $id
  482. };
  483. my $event = {
  484. "name" => $obj->{method},
  485. "type" => "musicvideo",
  486. "event" => 'Player.OnPlay'
  487. };
  488. $hash->{PendingEvents}{$id} = $event;
  489. XBMC_Call($hash, $req,1);
  490. }
  491. }
  492. sub XBMC_ProcessNotification($$)
  493. {
  494. my ($hash,$obj) = @_;
  495. my $name = $hash->{NAME};
  496. #React on volume change - http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Application.OnVolumeChanged
  497. if($obj->{method} eq "Application.OnVolumeChanged") {
  498. readingsBeginUpdate($hash);
  499. readingsBulkUpdate($hash,'volume',sprintf("%.2f", $obj->{params}->{data}->{volume}));
  500. readingsBulkUpdate($hash,'mute',($obj->{params}->{data}->{muted} ? 'on' : 'off'));
  501. readingsEndUpdate($hash, 1);
  502. }
  503. #React on play, pause and stop
  504. #http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Player.OnPlay
  505. #http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Player.OnPause
  506. #http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Player.OnStop
  507. elsif($obj->{method} eq "Player.OnPropertyChanged") {
  508. XBMC_PlayerUpdate($hash,$obj->{params}->{data}->{player}->{playerid});
  509. }
  510. elsif($obj->{method} =~ /(Player\.OnSeek|Player\.OnSpeedChanged|Player\.OnPropertyChanged)/) {
  511. my $base = $obj->{params}->{data}->{player};
  512. readingsBeginUpdate($hash);
  513. foreach my $key (keys %$base) {
  514. my $item = $base->{$key};
  515. XBMC_CreateReading($hash,$key,$item);
  516. }
  517. readingsEndUpdate($hash, 1);
  518. }
  519. elsif($obj->{method} eq "Player.OnStop") {
  520. readingsSingleUpdate($hash,"playStatus",'stopped',1);
  521. #HACK: We want to fetch GUI.Properties here to update for example stereoscopicmode.
  522. # When doing this here we still get the in-movie stereo mode. So we define a timer
  523. # to invoke the update in some (tm) seconds
  524. XBMC_QueueIntervalUpdate($hash, 2);
  525. }
  526. elsif($obj->{method} eq "Player.OnPause") {
  527. readingsSingleUpdate($hash,"playStatus",'paused',1);
  528. }
  529. elsif($obj->{method} eq "Player.OnPlay") {
  530. XBMC_ResetMediaReadings($hash);
  531. XBMC_PlayerOnPlay($hash, $obj);
  532. XBMC_Update($hash);
  533. }
  534. elsif($obj->{method} =~ /(Playlist|AudioLibrary|VideoLibrary|System).On(.*)/) {
  535. readingsSingleUpdate($hash,lc($1),lc($2),1);
  536. if (lc($1) eq "system") {
  537. if ((lc($2) eq "quit") or (lc($2) eq "restart") or (lc($2) eq "sleep")) {
  538. readingsSingleUpdate($hash, "playStatus", "stopped", 1);
  539. }
  540. if (lc($2) eq "sleep") {
  541. Log3($name, 3, "XBMC notified that it is going to sleep");
  542. #if we immediatlely close our DevIO then fhem will instantly try to reconnect which might
  543. #succeed because XBMC needs a moment to actually shutdown.
  544. #So cancel the current timer, fake that the last data has arrived ages ago
  545. #and force a connection check in some seconds when we think XBMC actually has shut down
  546. $hash->{LAST_RECV} = 0;
  547. RemoveInternalTimer($hash);
  548. XBMC_QueueIntervalUpdate($hash, 5);
  549. }
  550. }
  551. }
  552. return undef;
  553. }
  554. sub XBMC_ProcessResponse($$)
  555. {
  556. my ($hash,$obj) = @_;
  557. my $name = $hash->{NAME};
  558. my $id = $obj->{id};
  559. #check if the id of the answer matches the id of a pending event
  560. if(defined($hash->{PendingEvents}{$id})) {
  561. my $event = $hash->{PendingEvents}{$id};
  562. my $name = $event->{name};
  563. my $type = $event->{type};
  564. my $value = '';
  565. my $base = '';
  566. $base = $obj->{result}->{songdetails} if($type eq 'song');
  567. $base = $obj->{result}->{episodedetails} if($type eq 'episode');
  568. $base = $obj->{result}->{moviedetails} if($type eq 'movie');
  569. $base = $obj->{result}->{musicvideodetails} if($type eq 'musicvideo');
  570. if($base) {
  571. readingsBeginUpdate($hash);
  572. readingsBulkUpdate($hash,'playStatus','playing') if($event->{event} eq 'Player.OnPlay');
  573. readingsBulkUpdate($hash,'type',$type);
  574. foreach my $key (keys %$base) {
  575. my $item = $base->{$key};
  576. XBMC_CreateReading($hash,$key,$item);
  577. }
  578. readingsEndUpdate($hash, 1);
  579. }
  580. $hash->{PendingEvents}{$id} = undef;
  581. }
  582. elsif(exists($hash->{PendingPlayerCMDs}{$id})) {
  583. my $cmd = $hash->{PendingPlayerCMDs}{$id};
  584. my $players = $obj->{result};
  585. if (ref($players) ne "ARRAY") {
  586. my $keys = "";
  587. while ((my $k, my $v) = each %{ $hash->{PendingPlayerCMDs} } ) {
  588. $keys .= ",$k";
  589. }
  590. delete $hash->{PendingPlayerCMDs}{$id};
  591. Log3($name, 2, "XBMC_ProcessResponse: Not received a player array! Pending command cancelled!");
  592. Log3($name, 2, "XBMC_ProcessResponse: Keys in PendingPlayerCMDs: $keys");
  593. return -1;
  594. }
  595. foreach my $player (@$players) {
  596. $cmd->{id} = XBMC_CreateId($hash);
  597. $cmd->{params}->{playerid} = $player->{playerid};
  598. XBMC_Call($hash,$cmd,1);
  599. }
  600. delete $hash->{PendingPlayerCMDs}{$id};
  601. }
  602. else {
  603. my $result = $obj->{result};
  604. if($result && $result ne 'OK') {
  605. readingsBeginUpdate($hash);
  606. foreach my $key (keys %$result) {
  607. if ($key eq 'item') {
  608. my $item = $obj->{result}->{item};
  609. foreach my $ikey (keys %$item) {
  610. my $value = $item->{$ikey};
  611. XBMC_CreateReading($hash,$ikey,$value);
  612. }
  613. }
  614. else {
  615. my $value = $result->{$key};
  616. XBMC_CreateReading($hash,$key,$value);
  617. }
  618. }
  619. readingsEndUpdate($hash, 1);
  620. }
  621. }
  622. return 0;
  623. }
  624. sub XBMC_Is3DFile($$) {
  625. my ($hash, $filename) = @_;
  626. return ($filename =~ /([-. _]3d[-. _]|.*3dbd.*)/i);
  627. }
  628. sub XBMC_CreateReading($$$);
  629. sub XBMC_CreateReading($$$) {
  630. my $hash = shift;
  631. my $name = $hash->{NAME};
  632. my $key = shift;
  633. my $value = shift;
  634. return if ($key =~ /(playerid)/);
  635. if($key eq 'version') {
  636. my $version = '';
  637. $version = $value->{major};
  638. $version .= '.' . $value->{minor} if(defined($value->{minor}));
  639. $version .= '-' . $value->{revision} if(defined($value->{revision}));
  640. $version .= ' ' . $value->{tag} if(defined($value->{tag}));
  641. $value = $version;
  642. }
  643. elsif($key eq 'skin') {
  644. $value = $value->{name} . '(' . $value->{id} . ')';
  645. }
  646. elsif($key =~ /(totaltime|time|seekoffset)/) {
  647. $value = sprintf('%02d:%02d:%02d.%03d',$value->{hours},$value->{minutes},$value->{seconds},$value->{milliseconds});
  648. }
  649. elsif($key eq 'shuffled') {
  650. $key = 'shuffle';
  651. $value = ($value ? 'on' : 'off');
  652. }
  653. elsif($key eq 'muted') {
  654. $key = 'mute';
  655. $value = ($value ? 'on' : 'off');
  656. }
  657. elsif($key eq 'speed') {
  658. readingsBulkUpdate($hash,'playStatus','playing') if $value != 0;
  659. readingsBulkUpdate($hash,'playStatus','paused') if $value == 0;
  660. }
  661. elsif($key =~ /(fullscreen|partymode)/) {
  662. $value = ($value ? 'on' : 'off');
  663. }
  664. elsif($key eq 'file') {
  665. $key = 'currentMedia';
  666. readingsBulkUpdate($hash,'3dfile', XBMC_Is3DFile($hash, $value) ? "on" : "off");
  667. }
  668. elsif($key =~ /(album|artist|track|title)/) {
  669. $value = "" if $value eq -1;
  670. $key = 'current' . ucfirst($key);
  671. }
  672. elsif($key eq 'streamdetails') {
  673. foreach my $mediakey (keys %{$value}) {
  674. my $arrRef = $value->{$mediakey};
  675. for (my $i = 0; $i <= $#$arrRef; $i++) {
  676. my $propRef = $arrRef->[$i];
  677. foreach my $propkey (keys %{$propRef}) {
  678. readingsBulkUpdate($hash, "sd_" . $mediakey . $i . $propkey, $propRef->{$propkey});
  679. }
  680. }
  681. }
  682. # we dont want to create a "streamdetails" reading
  683. $key = undef;
  684. }
  685. elsif($key eq 'stereoscopicmode') {
  686. $value = $value->{mode};
  687. }
  688. if(ref($value) eq 'ARRAY') {
  689. $value = join(',',@$value);
  690. }
  691. if (defined $key) {
  692. if ($key =~ /(seekoffset)/) {
  693. # for these readings we do only events - no readings
  694. DoTrigger($name, "$key: $value");
  695. }
  696. else {
  697. readingsBulkUpdate($hash,$key,$value) ;
  698. }
  699. }
  700. }
  701. #Parses a given string and returns ($msg,$tail). If the string contains a complete message
  702. #(equal number of curly brackets) the return value $msg will contain this message. The
  703. #remaining string is return in form of the $tail variable.
  704. sub XBMC_ParseMsg($$)
  705. {
  706. my ($hash, $buffer) = @_;
  707. my $name = $hash->{NAME};
  708. my $open = 0;
  709. my $close = 0;
  710. my $msg = '';
  711. my $tail = '';
  712. if($buffer) {
  713. foreach my $c (split //, $buffer) {
  714. if($open == $close && $open > 0) {
  715. $tail .= $c;
  716. }
  717. elsif(($open == $close) && ($c ne '{')) {
  718. Log3($name, 3, "XBMC_ParseMsg: Garbage character before message: " . $c);
  719. }
  720. else {
  721. if($c eq '{') {
  722. $open++;
  723. }
  724. elsif($c eq '}') {
  725. $close++;
  726. }
  727. $msg .= $c;
  728. }
  729. }
  730. if($open != $close) {
  731. $tail = $msg;
  732. $msg = '';
  733. }
  734. }
  735. return ($msg,$tail);
  736. }
  737. sub XBMC_Set($@)
  738. {
  739. my ($hash, $name, $cmd, @args) = @_;
  740. if($cmd eq "off") {
  741. $cmd = AttrVal($hash->{NAME},'offMode','quit');
  742. }
  743. if($cmd eq 'statusRequest') {
  744. return XBMC_Update($hash);
  745. }
  746. #RPC referring to the Player - http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Player
  747. elsif($cmd eq 'playpause') {
  748. return XBMC_Set_PlayPause($hash,@args);
  749. }
  750. elsif($cmd eq 'play') {
  751. return XBMC_Set_PlayPause($hash,1, @args);
  752. }
  753. elsif($cmd eq 'pause') {
  754. return XBMC_Set_PlayPause($hash,0, @args);
  755. }
  756. elsif($cmd eq 'prev') {
  757. return XBMC_Set_Goto($hash,'previous', @args);
  758. }
  759. elsif($cmd eq 'next') {
  760. return XBMC_Set_Goto($hash,'next', @args);
  761. }
  762. elsif($cmd eq 'goto') {
  763. return XBMC_Set_Goto($hash, $args[0] - 1, $args[1]);
  764. }
  765. elsif($cmd eq 'stop') {
  766. return XBMC_Set_Stop($hash, @args);
  767. }
  768. elsif($cmd eq 'opendir') {
  769. return XBMC_Set_Open($hash, 'dir', @args);
  770. }
  771. elsif($cmd eq 'open') {
  772. return XBMC_Set_Open($hash, 'file', @args);
  773. }
  774. elsif($cmd eq 'openmovieid') {
  775. return XBMC_Set_Open($hash, 'movie', @args);
  776. }
  777. elsif($cmd eq 'openepisodeid') {
  778. return XBMC_Set_Open($hash, 'episode', @args);
  779. }
  780. elsif($cmd eq 'openchannelid') {
  781. return XBMC_Set_Open($hash, 'channel', @args);
  782. }
  783. elsif($cmd eq 'addon') {
  784. return XBMC_Set_Addon($hash, @args);
  785. }
  786. elsif($cmd eq 'shuffle') {
  787. return XBMC_Set_Shuffle($hash, @args);
  788. }
  789. elsif($cmd eq 'repeat') {
  790. return XBMC_Set_Repeat($hash, @args);
  791. }
  792. elsif($cmd eq 'seek') {
  793. return XBMC_Set_Seek($hash, $args[0], @args);
  794. }
  795. #RPC referring to the Input http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Input
  796. elsif($cmd eq 'back') {
  797. return XBMC_Simple_Call($hash,'Input.Back');
  798. }
  799. elsif($cmd eq 'contextmenu') {
  800. return XBMC_Simple_Call($hash,'Input.ContextMenu');
  801. }
  802. elsif($cmd eq 'down') {
  803. return XBMC_Simple_Call($hash,'Input.Down');
  804. }
  805. elsif($cmd eq 'home') {
  806. return XBMC_Simple_Call($hash,'Input.Home');
  807. }
  808. elsif($cmd eq 'info') {
  809. return XBMC_Simple_Call($hash,'Input.Info');
  810. }
  811. elsif($cmd eq 'left') {
  812. return XBMC_Simple_Call($hash,'Input.Left');
  813. }
  814. elsif($cmd eq 'right') {
  815. return XBMC_Simple_Call($hash,'Input.Right');
  816. }
  817. elsif($cmd eq 'select') {
  818. return XBMC_Simple_Call($hash,'Input.Select');
  819. }
  820. elsif($cmd eq 'send') {
  821. my $text = join(' ', @args);
  822. return XBMC_Call($hash,{'method' => 'Input.SendText', 'params' => { 'text' => $text}},0);
  823. }
  824. elsif($cmd eq 'exec') {
  825. my $action = $args[0]; #http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Input.Action
  826. return XBMC_Call($hash,{'method' => 'Input.ExecuteAction', 'params' => { 'action' => $action}},0);
  827. }
  828. elsif($cmd eq 'jsonraw') {
  829. my $action = join("",@args);
  830. return XBMC_Call_raw($hash,$action,0);
  831. }
  832. elsif($cmd eq 'showcodec') {
  833. return XBMC_Simple_Call($hash,'Input.ShowCodec');
  834. }
  835. elsif($cmd eq 'showosd') {
  836. return XBMC_Simple_Call($hash,'Input.ShowOSD');
  837. }
  838. elsif($cmd eq 'up') {
  839. return XBMC_Simple_Call($hash,'Input.Up');
  840. }
  841. #RPC referring to the GUI - http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#GUI
  842. elsif($cmd eq 'msg') {
  843. return XBMC_Set_Message($hash,@args);
  844. }
  845. #RPC referring to the Application - http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Application
  846. elsif($cmd eq 'mute') {
  847. return XBMC_Set_Mute($hash,@args);
  848. }
  849. elsif($cmd eq 'volume') {
  850. return XBMC_Call($hash,{'method' => 'Application.SetVolume', 'params' => { 'volume' => int($args[0])}},0);
  851. }
  852. elsif($cmd eq 'volumeUp') {
  853. return XBMC_Call($hash,{'method' => 'Input.ExecuteAction', 'params' => { 'action' => 'volumeup'}},0);
  854. }
  855. elsif($cmd eq 'volumeDown') {
  856. return XBMC_Call($hash,{'method' => 'Input.ExecuteAction', 'params' => { 'action' => 'volumedown'}},0);
  857. }
  858. elsif($cmd eq 'quit') {
  859. return XBMC_Simple_Call($hash,'Application.Quit');
  860. }
  861. #RPC referring to the System - http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#System
  862. elsif($cmd eq 'eject') {
  863. return XBMC_Simple_Call($hash,'System.EjectOpticalDrive');
  864. }
  865. elsif($cmd eq 'hibernate') {
  866. return XBMC_Simple_Call($hash,'System.Hibernate');
  867. }
  868. elsif($cmd eq 'reboot') {
  869. return XBMC_Simple_Call($hash,'System.Reboot');
  870. }
  871. elsif($cmd eq 'shutdown') {
  872. return XBMC_Simple_Call($hash,'System.Shutdown');
  873. }
  874. elsif($cmd eq 'suspend') {
  875. return XBMC_Simple_Call($hash,'System.Suspend');
  876. }
  877. #RPC referring to the VideoLibary - http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#VideoLibrary
  878. elsif($cmd eq 'videolibrary') {
  879. my $opt = $args[0];
  880. if($opt eq 'clean') {
  881. return XBMC_Simple_Call($hash,'VideoLibrary.Clean');
  882. }
  883. elsif($opt eq 'scan') {
  884. return XBMC_Simple_Call($hash,'VideoLibrary.Scan');
  885. }
  886. }
  887. #RPC referring to the AudioLibary - http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#AudioLibrary
  888. elsif($cmd eq 'audiolibrary') {
  889. my $opt = $args[0];
  890. if($opt eq 'clean') {
  891. return XBMC_Simple_Call($hash,'AudioLibrary.Clean');
  892. }
  893. elsif($opt eq 'scan') {
  894. return XBMC_Simple_Call($hash,'AudioLibrary.Scan');
  895. }
  896. }
  897. elsif($cmd eq 'connect') {
  898. return XBMC_Connect($hash);
  899. }
  900. my $res = "Unknown argument " . $cmd . ", choose one of " .
  901. "off play:all,audio,video,picture playpause:all,audio,video,picture pause:all,audio,video,picture " .
  902. "prev:all,audio,video,picture next:all,audio,video,picture goto stop:all,audio,video,picture " .
  903. "open opendir openmovieid openepisodeid openchannelid addon shuffle:toggle,on,off repeat:one,all,off volumeUp:noArg volumeDown:noArg " .
  904. "seek back:noArg contextmenu:noArg down:noArg home:noArg info:noArg left:noArg " .
  905. "right:noArg select:noArg send exec:left,right," .
  906. "up,down,pageup,pagedown,select,highlight,parentdir,parentfolder,back," .
  907. "previousmenu,info,pause,stop,skipnext,skipprevious,fullscreen,aspectratio," .
  908. "stepforward,stepback,bigstepforward,bigstepback,osd,showsubtitles," .
  909. "nextsubtitle,codecinfo,nextpicture,previouspicture,zoomout,zoomin," .
  910. "playlist,queue,zoomnormal,zoomlevel1,zoomlevel2,zoomlevel3,zoomlevel4," .
  911. "zoomlevel5,zoomlevel6,zoomlevel7,zoomlevel8,zoomlevel9,nextcalibration," .
  912. "resetcalibration,analogmove,rotate,rotateccw,close,subtitledelayminus," .
  913. "subtitledelay,subtitledelayplus,audiodelayminus,audiodelay,audiodelayplus," .
  914. "subtitleshiftup,subtitleshiftdown,subtitlealign,audionextlanguage," .
  915. "verticalshiftup,verticalshiftdown,nextresolution,audiotoggledigital," .
  916. "number0,number1,number2,number3,number4,number5,number6,number7," .
  917. "number8,number9,osdleft,osdright,osdup,osddown,osdselect,osdvalueplus," .
  918. "osdvalueminus,smallstepback,fastforward,rewind,play,playpause,delete," .
  919. "copy,move,mplayerosd,hidesubmenu,screenshot,rename,togglewatched,scanitem," .
  920. "reloadkeymaps,volumeup,volumedown,mute,backspace,scrollup,scrolldown," .
  921. "analogfastforward,analogrewind,moveitemup,moveitemdown,contextmenu,shift," .
  922. "symbols,cursorleft,cursorright,showtime,analogseekforward,analogseekback," .
  923. "showpreset,presetlist,nextpreset,previouspreset,lockpreset,randompreset," .
  924. "increasevisrating,decreasevisrating,showvideomenu,enter,increaserating," .
  925. "decreaserating,togglefullscreen,nextscene,previousscene,nextletter,prevletter," .
  926. "jumpsms2,jumpsms3,jumpsms4,jumpsms5,jumpsms6,jumpsms7,jumpsms8,jumpsms9,filter," .
  927. "filterclear,filtersms2,filtersms3,filtersms4,filtersms5,filtersms6,filtersms7," .
  928. "filtersms8,filtersms9,firstpage,lastpage,guiprofile,red,green,yellow,blue," .
  929. "increasepar,decreasepar,volampup,volampdown,channelup,channeldown," .
  930. "previouschannelgroup,nextchannelgroup,leftclick,rightclick,middleclick," .
  931. "doubleclick,wheelup,wheeldown,mousedrag,mousemove,noop showcodec:noArg showosd:noArg up:noArg " .
  932. "msg " .
  933. "mute:toggle,on,off volume:slider,0,1,100 quit:noArg " .
  934. "eject:noArg hibernate:noArg reboot:noArg shutdown:noArg suspend:noArg " .
  935. "videolibrary:scan,clean audiolibrary:scan,clean statusRequest jsonraw " .
  936. "connect:noArg";
  937. return $res ;
  938. }
  939. sub XBMC_Simple_Call($$) {
  940. my ($hash,$method) = @_;
  941. return XBMC_Call($hash,{'method' => $method},0);
  942. }
  943. sub XBMC_Set_Open($@)
  944. {
  945. my $hash = shift;
  946. my $opt = shift;
  947. my $params;
  948. my $path = join(' ', @_);
  949. $path = $1 if ($path =~ /^['"](.*)['"]$/);
  950. $path =~ s/\\/\\\\/g;
  951. if($opt eq 'file') {
  952. $params = {
  953. 'item' => {
  954. 'file' => $path
  955. }
  956. };
  957. } elsif($opt eq 'dir') {
  958. $params = {
  959. 'item' => {
  960. 'directory' => $path
  961. }
  962. };
  963. } elsif($opt eq 'movie') {
  964. $params = {
  965. 'item' => {
  966. 'movieid' => $path +0
  967. },
  968. 'options' => {
  969. 'resume' => JSON::true
  970. }
  971. };
  972. } elsif($opt eq 'episode') {
  973. $params = {
  974. 'item' => {
  975. 'episodeid' => $path +0
  976. },
  977. 'options' => {
  978. 'resume' => JSON::true
  979. }
  980. };
  981. } elsif($opt eq 'channel') {
  982. $params = {
  983. 'item' => {
  984. 'channelid' => $path +0
  985. },
  986. };
  987. }
  988. my $obj = {
  989. 'method' => 'Player.Open',
  990. 'params' => $params
  991. };
  992. return XBMC_Call($hash,$obj,0);
  993. }
  994. sub XBMC_Set_Addon($@)
  995. {
  996. my $hash = shift;
  997. my $params;
  998. my $attr = join(" ", @_);
  999. $attr =~ /(".*?"|'.*?'|[^ ]+)[ \t]+(".*?"|'.*?'|[^ ]+)[ \t]+(".*?"|'.*?'|[^ ]+)$/;
  1000. my $addonid = $1;
  1001. my $paramname = $2;
  1002. my $paramvalue = $3;
  1003. # printf "$1 $2 $3";
  1004. $params = {
  1005. 'addonid' => $addonid,
  1006. 'params' => {
  1007. $paramname => $paramvalue
  1008. }
  1009. };
  1010. my $obj = {
  1011. 'method' => 'Addons.ExecuteAddon',
  1012. 'params' => $params
  1013. };
  1014. return XBMC_Call($hash,$obj,0);
  1015. }
  1016. sub XBMC_Set_Message($@)
  1017. {
  1018. my $hash = shift;
  1019. my $attr = join(" ", @_);
  1020. $attr =~ /(".*?"|'.*?'|[^ ]+)[ \t]+(".*?"|'.*?'|[^ ]+)([ \t]+([0-9]+))?([ \t]+([^ ]*))?$/;
  1021. my $title = $1;
  1022. my $message = $2;
  1023. my $duration = $4;
  1024. my $image = $6;
  1025. if($title =~ /^['"](.*)['"]$/) {
  1026. $title = $1;
  1027. }
  1028. if($message =~ /^['"](.*)['"]$/) {
  1029. $message = $1;
  1030. }
  1031. my $obj = {
  1032. 'method' => 'GUI.ShowNotification',
  1033. 'params' => {
  1034. 'title' => $title,
  1035. 'message' => $message
  1036. }
  1037. };
  1038. if($duration && $duration =~ /[0-9]+/ && int($duration) >= 1500) {
  1039. $obj->{params}->{displaytime} = int($duration);
  1040. }
  1041. if($image) {
  1042. $obj->{params}->{image} = $image;
  1043. }
  1044. return XBMC_Call($hash, $obj,0);
  1045. }
  1046. sub XBMC_Set_Stop($@)
  1047. {
  1048. my ($hash,$player) = @_;
  1049. my $obj = {
  1050. 'method' => 'Player.Stop',
  1051. 'params' => {
  1052. 'playerid' => 0 #will be replaced with the active player
  1053. }
  1054. };
  1055. return XBMC_PlayerCommand($hash,$obj,$player);
  1056. }
  1057. sub XBMC_Set_Seek($@)
  1058. {
  1059. my ($hash,$position,$player) = @_;
  1060. my ($hours, $minutes, $seconds) = split(/:/, $position);
  1061. my $obj = {
  1062. 'method' => 'Player.Seek',
  1063. 'params' => {
  1064. 'value' => {
  1065. 'seconds' => $seconds + 0,
  1066. 'minutes' => $minutes + 0 ,
  1067. 'hours' => $hours + 0
  1068. },
  1069. 'playerid' => 0 #will be replaced with the active player
  1070. }
  1071. };
  1072. return XBMC_PlayerCommand($hash,$obj,$player);
  1073. }
  1074. sub XBMC_Set_Goto($$$)
  1075. {
  1076. my ($hash,$direction,$player) = @_;
  1077. my $obj = {
  1078. 'method' => 'Player.GoTo',
  1079. 'params' => {
  1080. 'to' => $direction,
  1081. 'playerid' => -1 #will be replaced with the active player
  1082. }
  1083. };
  1084. return XBMC_PlayerCommand($hash,$obj,$player);
  1085. }
  1086. sub XBMC_Set_Shuffle($@)
  1087. {
  1088. my ($hash,@args) = @_;
  1089. my $toggle = 'toggle';
  1090. my $player = '';
  1091. if(int(@args) >= 2) {
  1092. $toggle = $args[0];
  1093. $player = $args[1];
  1094. }
  1095. elsif(int(@args) == 1) {
  1096. if($args[0] =~ /(all|audio|video|picture)/) {
  1097. $player = $args[0];
  1098. }
  1099. else {
  1100. $toggle = $args[0];
  1101. }
  1102. }
  1103. my $type = XBMC_Toggle($toggle);
  1104. my $obj = {
  1105. 'method' => 'Player.SetShuffle',
  1106. 'params' => {
  1107. 'shuffle' => $type,
  1108. 'playerid' => -1 #will be replaced with the active player
  1109. }
  1110. };
  1111. return XBMC_PlayerCommand($hash,$obj,$player);
  1112. }
  1113. sub XBMC_Set_Repeat($@)
  1114. {
  1115. my ($hash,$opt,@args) = @_;
  1116. $opt = 'off' if($opt !~ /(one|all|off)/);
  1117. my $player = '';
  1118. if(int(@args) == 1 && $args[0] =~ /(all|audio|video|picture)/) {
  1119. $player = $args[0];
  1120. }
  1121. my $obj = {
  1122. 'method' => 'Player.SetRepeat',
  1123. 'params' => {
  1124. 'repeat' => $opt,
  1125. 'playerid' => -1 #will be replaced with the active player
  1126. }
  1127. };
  1128. return XBMC_PlayerCommand($hash,$obj,$player);
  1129. }
  1130. sub XBMC_Set_PlayPause($@)
  1131. {
  1132. my ($hash,@args) = @_;
  1133. my $toggle = 'toggle';
  1134. my $player = '';
  1135. if(int(@args) >= 2) {
  1136. $toggle = $args[0];
  1137. $player = $args[1];
  1138. }
  1139. elsif(int(@args) == 1) {
  1140. if($args[0] =~ /(all|audio|video|picture)/) {
  1141. $player = $args[0];
  1142. }
  1143. else {
  1144. $toggle = $args[0];
  1145. }
  1146. }
  1147. my $type = XBMC_Toggle($toggle);
  1148. my $obj = {
  1149. 'method' => 'Player.PlayPause',
  1150. 'params' => {
  1151. 'play' => $type,
  1152. 'playerid' => -1 #will be replaced with the active player
  1153. }
  1154. };
  1155. return XBMC_PlayerCommand($hash,$obj,$player);
  1156. }
  1157. sub XBMC_PlayerCommand($$$)
  1158. {
  1159. my ($hash,$obj,$player) = @_;
  1160. if($player) {
  1161. my $id = -1;
  1162. $id = 0 if($player eq "audio");
  1163. $id = 1 if($player eq "video");
  1164. $id = 2 if($player eq "picture");
  1165. if($id > 0 && $id < 3) {
  1166. $obj->{params}->{playerid} = $id;
  1167. return XBMC_Call($hash, $obj,0);
  1168. }
  1169. }
  1170. #we need to find out the correct player first
  1171. my $id = XBMC_CreateId($hash);
  1172. $hash->{PendingPlayerCMDs}->{$id} = $obj;
  1173. my $req = {
  1174. 'method' => 'Player.GetActivePlayers',
  1175. 'id' => $id
  1176. };
  1177. return XBMC_Call($hash,$req,1);
  1178. }
  1179. #returns 'toggle' if the argument is undef
  1180. #returns JSON::true if the argument is true and not equals "off" otherwise it returns JSON::false
  1181. sub XBMC_Toggle($)
  1182. {
  1183. my ($toggle) = @_;
  1184. if(defined($toggle) && $toggle ne "toggle") {
  1185. if($toggle && $toggle ne "off") {
  1186. return JSON::true;
  1187. }
  1188. else {
  1189. return JSON::false;
  1190. }
  1191. }
  1192. return 'toggle';
  1193. }
  1194. sub XBMC_Set_Mute($@)
  1195. {
  1196. my ($hash,$toggle) = @_;
  1197. my $type = XBMC_Toggle($toggle);
  1198. my $obj = {
  1199. 'method' => 'Application.SetMute',
  1200. 'params' => { 'mute' => $type}
  1201. };
  1202. return XBMC_Call($hash, $obj,0);
  1203. }
  1204. #Executes a JSON RPC
  1205. sub XBMC_Call($$$)
  1206. {
  1207. my ($hash,$obj,$id) = @_;
  1208. my $name = $hash->{NAME};
  1209. #add an ID otherwise XBMC will not respond
  1210. if($id &&!defined($obj->{id})) {
  1211. $obj->{id} = XBMC_CreateId($hash);
  1212. }
  1213. $obj->{jsonrpc} = "2.0"; #JSON RPC version has to be passed
  1214. my $json = JSON->new->utf8(0)->encode($obj);
  1215. Log3($name, 4, "XBMC_Call: Sending: " . $json);
  1216. if($hash->{Protocol} eq 'http') {
  1217. return XBMC_HTTP_Call($hash,$json,$id);
  1218. }
  1219. else {
  1220. return XBMC_TCP_Call($hash,$json);
  1221. }
  1222. }
  1223. sub XBMC_Call_raw($$$)
  1224. {
  1225. my ($hash,$obj,$id) = @_;
  1226. my $name = $hash->{NAME};
  1227. Log3($name, 5, "XBMC_Call: Sending: " . $obj);
  1228. if($hash->{Protocol} eq 'http') {
  1229. return XBMC_HTTP_Call($hash,$obj,$id);
  1230. }
  1231. else {
  1232. return XBMC_TCP_Call($hash,$obj);
  1233. }
  1234. }
  1235. sub XBMC_RCmakenotify($$) {
  1236. my ($nam, $ndev) = @_;
  1237. my $nname="notify_$nam";
  1238. fhem("define $nname notify $nam set $ndev ".'$EVENT',1);
  1239. return "Notify created by XBMC: $nname";
  1240. }
  1241. sub XBMC_RClayout() {
  1242. my @row;
  1243. my $i = 0;
  1244. $row[$i++] = "showosd:MENU,up:UP,home:HOMEsym,exec volumeup:VOLUP";
  1245. $row[$i++] = "left:LEFT,select:OK,right:RIGHT,mute:MUTE";
  1246. $row[$i++] = "info:INFO,down:DOWN,back:RETURN,exec volumedown:VOLDOWN";
  1247. $row[$i++] = "exec stepback:REWIND,playpause:PLAY,stop:STOP,exec stepforward:FF";
  1248. $row[$i++] = "attr rc_iconpath icons/remotecontrol";
  1249. $row[$i++] = "attr rc_iconprefix black_btn_";
  1250. return @row;
  1251. }
  1252. #JSON RPC over TCP
  1253. sub XBMC_TCP_Call($$)
  1254. {
  1255. my ($hash,$obj) = @_;
  1256. return DevIo_SimpleWrite($hash,$obj,'');
  1257. }
  1258. #JSON RPC over HTTP
  1259. sub XBMC_HTTP_Call($$$)
  1260. {
  1261. my ($hash,$obj,$id) = @_;
  1262. my $uri = "http://" . $hash->{Host} . ":" . $hash->{Port} . "/jsonrpc";
  1263. my $ret = XBMC_HTTP_Request(0,$uri,undef,$obj,undef,$hash->{Username},$hash->{Password});
  1264. return undef if(!$ret);
  1265. if($ret =~ /^error:(\d{3})$/) {
  1266. return "HTTP Error Code " . $1;
  1267. }
  1268. return XBMC_ProcessResponse($hash,JSON->new->utf8(0)->decode($ret)) if($id);
  1269. return undef;
  1270. }
  1271. #adapted version of the CustomGetFileFromURL subroutine from HttpUtils.pm
  1272. sub XBMC_HTTP_Request($$@)
  1273. {
  1274. my ($quiet, $url, $timeout, $data, $noshutdown,$username,$password) = @_;
  1275. $timeout = 4.0 if(!defined($timeout));
  1276. my $displayurl= $quiet ? "<hidden>" : $url;
  1277. if($url !~ /^(http|https):\/\/([^:\/]+)(:\d+)?(\/.*)$/) {
  1278. Log(1, "XBMC_HTTP_Request $displayurl: malformed or unsupported URL");
  1279. return undef;
  1280. }
  1281. my ($protocol,$host,$port,$path)= ($1,$2,$3,$4);
  1282. if(defined($port)) {
  1283. $port =~ s/^://;
  1284. } else {
  1285. $port = ($protocol eq "https" ? 443: 80);
  1286. }
  1287. $path= '/' unless defined($path);
  1288. my $conn;
  1289. if($protocol eq "https") {
  1290. eval "use IO::Socket::SSL";
  1291. if($@) {
  1292. Log(1, $@);
  1293. } else {
  1294. $conn = IO::Socket::SSL->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
  1295. }
  1296. } else {
  1297. $conn = IO::Socket::INET->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
  1298. }
  1299. if(!$conn) {
  1300. Log(1, "XBMC_HTTP_Request $displayurl: Can't connect to $protocol://$host:$port\n");
  1301. undef $conn;
  1302. return undef;
  1303. }
  1304. $host =~ s/:.*//;
  1305. my $hdr = ($data ? "POST" : "GET")." $path HTTP/1.0\r\nHost: $host\r\n";
  1306. if($username) {
  1307. $hdr .= "Authorization: Basic ";
  1308. if($password) {
  1309. $hdr .= encode_base64($username . ":" . $password,"\r\n");
  1310. }
  1311. else {
  1312. $hdr .= encode_base64($username,"\r\n");
  1313. }
  1314. }
  1315. if(defined($data)) {
  1316. $hdr .= "Content-Length: ".length($data)."\r\n";
  1317. $hdr .= "Content-Type: application/json";
  1318. }
  1319. $hdr .= "\r\n\r\n";
  1320. syswrite $conn, $hdr;
  1321. syswrite $conn, $data if(defined($data));
  1322. shutdown $conn, 1 if(!$noshutdown);
  1323. my ($buf, $ret) = ("", "");
  1324. $conn->timeout($timeout);
  1325. for(;;) {
  1326. my ($rout, $rin) = ('', '');
  1327. vec($rin, $conn->fileno(), 1) = 1;
  1328. my $nfound = select($rout=$rin, undef, undef, $timeout);
  1329. if($nfound <= 0) {
  1330. Log(1, "XBMC_HTTP_Request $displayurl: Select timeout/error: $!");
  1331. undef $conn;
  1332. return undef;
  1333. }
  1334. my $len = sysread($conn,$buf,65536);
  1335. last if(!defined($len) || $len <= 0);
  1336. $ret .= $buf;
  1337. }
  1338. $ret=~ s/(.*?)\r\n\r\n//s; # Not greedy: switch off the header.
  1339. my @header= split("\r\n", $1);
  1340. my $hostpath= $quiet ? "<hidden>" : $host . $path;
  1341. Log(4, "XBMC_HTTP_Request $displayurl: Got data, length: ".length($ret));
  1342. if(!length($ret)) {
  1343. Log(4, "XBMC_HTTP_Request $displayurl: Zero length data, header follows...");
  1344. for (@header) {
  1345. Log(4, "XBMC_HTTP_Request $displayurl: $_");
  1346. }
  1347. }
  1348. undef $conn;
  1349. if($header[0] =~ /^[^ ]+ ([\d]{3})/ && $1 != 200) {
  1350. return "error:" . $1;
  1351. }
  1352. return $ret;
  1353. }
  1354. 1;
  1355. =pod
  1356. =item summary control and receive events from Kodi/XBMC
  1357. =item summary_DE Steuern und &uuml;berwachen von Kodi/XBMC
  1358. =begin html
  1359. <a name="XBMC"></a>
  1360. <h3>XBMC</h3>
  1361. <ul>
  1362. <a name="XBMCdefine"></a>
  1363. <b>Define</b>
  1364. <ul>
  1365. <code>define &lt;name&gt; XBMC &lt;ip[:port]&gt; &lt;http|tcp&gt; [&lt;username&gt;] [&lt;password&gt;]</code>
  1366. <br><br>
  1367. This module allows you to control XBMC and receive events from XBMC. It can also be used to control Plex (see attribute <i>compatibilityMode</i>).<br><br>
  1368. <b>Prerequisites</b>
  1369. <ul>
  1370. <li>Requires XBMC "Frodo" 12.0.</li>
  1371. <li>To use this module you will have to enable JSON-RPC. See <a href="http://wiki.xbmc.org/index.php?title=JSON-RPC_API#Enabling_JSON-RPC">here</a>.</li>
  1372. <li>The Perl module JSON is required. <br>
  1373. On Debian/Raspbian: <code>apt-get install libjson-perl </code><br>
  1374. Via CPAN: <code>cpan install JSON</code>
  1375. To get it working on a Fritzbox the JSON module has to be installed manually.</li>
  1376. </ul>
  1377. To receive events it is necessary to use TCP. The default TCP port is 9090. Username and password are optional for TCP. Be sure to enable JSON-RPC
  1378. for TCP. See <a href="http://wiki.xbmc.org/index.php?title=JSON-RPC_API#Enabling_JSON-RPC>here</a>.<br><br>
  1379. If you just want to control XBMC you can use the HTTP instead of tcp. The username and password are required for HTTP. Be sure to enable JSON-RPC for HTTP.
  1380. See <a href="http://wiki.xbmc.org/index.php?title=JSON-RPC_API#Enabling_JSON-RPC">here</a>.<br><br>
  1381. Example:<br><br>
  1382. <ul>
  1383. <code>
  1384. define htpc XBMC 192.168.0.10 tcp
  1385. <br><br>
  1386. define htpc XBMC 192.168.0.10:9000 tcp # With custom port
  1387. <br><br>
  1388. define htpc XBMC 192.168.0.10 http # Use HTTP instead of TCP - Note: to receive events use TCP!
  1389. <br><br>
  1390. define htpc XBMC 192.168.0.10 http xbmc passwd # Use HTTP with credentials - Note: to receive events use TCP!
  1391. </code>
  1392. </ul><br><br>
  1393. Remote control:<br>
  1394. There is an simple remote control layout for XBMC which contains the most basic buttons. To add the remote control to the webinterface execute the
  1395. following commands:<br><br>
  1396. <ul>
  1397. <code>
  1398. define &lt;rc_name&gt; remotecontrol #adds the remote control
  1399. <br><br>
  1400. set &lt;rc_name&gt; layout XBMC_RClayout #sets the layout for the remote control
  1401. <br><br>
  1402. set &lt;rc_name&gt; makenotify &lt;XBMC_device&gt; #links the buttons to the actions
  1403. </code>
  1404. </ul><br><br>
  1405. Known issues:<br>
  1406. XBMC sometimes creates events twices. For example the Player.OnPlay event is created twice if play a song. Unfortunately this
  1407. is a issue of XBMC. The fix of this bug is included in future version of XBMC (> 12.2).
  1408. </ul>
  1409. <a name="XBMCset"></a>
  1410. <b>Set</b>
  1411. <ul>
  1412. <code>set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]</code>
  1413. <br><br>
  1414. This module supports the following commands:<br>
  1415. Player related commands:<br>
  1416. <ul>
  1417. <li><b>play [&lt;all|audio|video|picture&gt;]</b> - starts the playback (might only work if previously paused). The second argument defines which player should be started. By default the active players will be started</li>
  1418. <li><b>pause [&lt;all|audio|video|picture&gt;]</b> - pauses the playback</li>
  1419. <li><b>playpause [&lt;all|audio|video|picture&gt;]</b> - toggles between play and pause for the given player</li>
  1420. <li><b>stop [&lt;all|audio|video|picture&gt;]</b> - stop the playback</li>
  1421. <li><b>next [&lt;all|audio|video|picture&gt;]</b> - jump to the next track</li>
  1422. <li><b>prev [&lt;all|audio|video|picture&gt;]</b> - jump to the previous track or the beginning of the current track.</li>
  1423. <li><b>goto &lt;position&gt; [&lt;audio|video|picture&gt;]</b> - Goes to the <position> in the playlist. <position> has to be a number.</li>
  1424. <li><b>shuffle [&lt;toggle|on|off&gt;] [&lt;audio|video|picture&gt;]</b> - Enables/Disables shuffle mode. Without furhter parameters the shuffle mode is toggled.</li>
  1425. <li><b>repeat &lt;one|all|off&gt; [&lt;audio|video|picture&gt;]</b> - Sets the repeat mode.</li>
  1426. <li><b>open &lt;URI&gt;</b> - Plays the resource located at the URI (can be a url or a file)</li>
  1427. <li><b>opendir &lt;path&gt;</b> - Plays the content of the directory</li>
  1428. <li><b>openmovieid &lt;path&gt;</b> - Plays a movie by id</li>
  1429. <li><b>openepisodeid &lt;path&gt;</b> - Plays an episode by id</li>
  1430. <li><b>openchannelid &lt;path&gt;</b> - Switches to channel by id</li>
  1431. <li><b>addon &lt;addonid&gt; &lt;parametername&gt; &lt;parametervalue&gt;</b> - Executes addon with one Parameter, for example set xbmc addon script.json-cec command activate</li>
  1432. <li><b>seek &lt;hh:mm:ss&gt;</b> - seek to the specified time</li>
  1433. </ul>
  1434. <br>Input related commands:<br>
  1435. <ul>
  1436. <li><b>back</b> - Back-button</li>
  1437. <li><b>down</b> - Down-button</li>
  1438. <li><b>up</b> - Up-button</li>
  1439. <li><b>left</b> - Left-button</li>
  1440. <li><b>right</b> - Right-button</li>
  1441. <li><b>home</b> - Home-button</li>
  1442. <li><b>select</b> - Select-button</li>
  1443. <li><b>info</b> - Info-button</li>
  1444. <li><b>showosd</b> - Opens the OSD (On Screen Display)</li>
  1445. <li><b>showcodec</b> - Shows Codec information</li>
  1446. <li><b>exec &lt;action&gt;</b> - Execute an input action. All available actions are listed <a href="http://wiki.xbmc.org/index.php?title=JSON-RPC_API/v6#Input.Action">here</a></li>
  1447. <li><b>send &lt;text&gt;</b> - Sends &lt;text&gt; as input to XBMC</li>
  1448. <li><b>jsonraw</b> - Sends raw JSON data to XBMC</li>
  1449. </ul>
  1450. <br>Libary related commands:<br>
  1451. <ul>
  1452. <li><b>videolibrary clean</b> - Removes non-existing files from the video libary</li>
  1453. <li><b>videolibrary scan</b> - Scan for new video files</li>
  1454. <li><b>audiolibrary clean</b> - Removes non-existing files from the audio libary</li>
  1455. <li><b>audiolibrary scan</b> - Scan for new audio files</li>
  1456. </ul>
  1457. <br>Application related commands:<br>
  1458. <ul>
  1459. <li><b>mute [&lt;0|1&gt;]</b> - 1 for mute; 0 for unmute; by default the mute status will be toggled</li>
  1460. <li><b>volume &lt;n&gt;</b> - sets the volume to &lt;n&gt;. &lt;n&gt; must be a number between 0 and 100</li>
  1461. <li><b>volumeDown &lt;n&gt;</b> - volume down</li>
  1462. <li><b>volumeUp &lt;n&gt;</b> - volume up</li>
  1463. <li><b>quit</b> - closes XBMC</li>
  1464. <li><b>off</b> - depending on the value of the attribute &quot;offMode&quot; XBMC will be closed (see quit) or the system will be shut down, put into hibernation or stand by. Default is quit.</li>
  1465. </ul>
  1466. <br>System related commands:<br>
  1467. <ul>
  1468. <li><b>eject</b> - will eject the optical drive</li>
  1469. <li><b>shutdown</b> - the XBMC host will be shut down</li>
  1470. <li><b>suspend</b> - the XBMC host will be put into stand by</li>
  1471. <li><b>hibernate</b> - the XBMC host will be put into hibernation</li>
  1472. <li><b>reboot</b> - the XBMC host will be rebooted</li>
  1473. <li><b>connect</b> - try to connect to the XBMC host immediately</li>
  1474. </ul>
  1475. </ul>
  1476. <br><br>
  1477. <u>Messaging</u>
  1478. <ul>
  1479. To show messages on XBMC (little message PopUp at the bottom right egde of the screen) you can use the following commands:<br>
  1480. <code>set &lt;XBMC_device&gt; msg &lt;title&gt; &lt;msg&gt; [&lt;duration&gt;] [&lt;icon&gt;]</code><br>
  1481. The default duration of a message is 5000 (5 seconds). The minimum duration is 1500 (1.5 seconds). By default no icon is shown. XBMC provides three
  1482. different icon: error, info and warning. You can also use an uri to define an icon. Please enclose title and/or message into quotes (" or ') if it consists
  1483. of multiple words.
  1484. </ul>
  1485. <br>
  1486. <b>Generated Readings/Events:</b><br>
  1487. <ul>
  1488. <li><b>audiolibrary</b> - Possible values: cleanfinished, cleanstarted, remove, scanfinished, scanstarted, update</li>
  1489. <li><b>currentAlbum</b> - album of the current song/musicvideo</li>
  1490. <li><b>currentArtist</b> - artist of the current song/musicvideo</li>
  1491. <li><b>currentMedia</b> - file/URL of the media item being played</li>
  1492. <li><b>currentTitle</b> - title of the current media item</li>
  1493. <li><b>currentTrack</b> - track of the current song/musicvideo</li>
  1494. <li><b>episode</b> - episode number</li>
  1495. <li><b>episodeid</b> - id of the episode in the video library</li>
  1496. <li><b>fullscreen</b> - indicates if XBMC runs in fullscreen mode (on/off)</li>
  1497. <li><b>label</b> - label of the current media item</li>
  1498. <li><b>movieid</b> - id of the movie in the video library</li>
  1499. <li><b>musicvideoid</b> - id of the musicvideo in the video library</li>
  1500. <li><b>mute</b> - indicates if XBMC is muted (on/off)</li>
  1501. <li><b>name</b> - software name (e.g. XBMC)</li>
  1502. <li><b>originaltitle</b> - original title of the movie being played</li>
  1503. <li><b>partymode</b> - indicates if XBMC runs in party mode (on/off) (not available for Plex)</li>
  1504. <li><b>playlist</b> - Possible values: add, clear, remove</li>
  1505. <li><b>playStatus</b> - Indicates the player status: playing, paused, stopped</li>
  1506. <li><b>repeat</b> - current repeat mode (one/all/off)</li>
  1507. <li><b>season</b> - season of the current episode</li>
  1508. <li><b>showtitle</b> - title of the show being played</li>
  1509. <li><b>shuffle</b> - indicates if the playback is shuffled (on/off)</li>
  1510. <li><b>skin</b> - current skin of XBMC</li>
  1511. <li><b>songid</b> - id of the song in the music library</li>
  1512. <li><b>system</b> - Possible values: lowbattery, quit, restart, sleep, wake</li>
  1513. <li><b>time</b> - current position in the playing media item (only updated on play/pause)</li>
  1514. <li><b>totaltime</b> - total run time of the current media item</li>
  1515. <li><b>type</b> - type of the media item. Possible values: episode, movie, song, musicvideo, picture, unknown</li>
  1516. <li><b>version</b> - version of XBMC</li>
  1517. <li><b>videolibrary</b> - Possible values: cleanfinished, cleanstarted, remove, scanfinished, scanstarted, update</li>
  1518. <li><b>volume</b> - value between 0 and 100 stating the current volume setting</li>
  1519. <li><b>year</b> - year of the movie being played</li>
  1520. <li><b>3dfile</b> - is a 3D movie according to filename</li>
  1521. <li><b>sd_<type><n>_<reading></b> - stream details of the current medium. type can be video, audio or subtitle, n is the stream index (a stream can have multiple audio/video streams)</li>
  1522. </ul>
  1523. <br><br>
  1524. <u>Remarks on the events</u><br><br>
  1525. <ul>
  1526. The event <b>playStatus = playing</b> indicates a playback of a media item. Depending on the event <b>type</b> different events are generated:
  1527. <ul>
  1528. <li><b>type = song</b> generated events are: <b>album, artist, file, title</b> and <b>track</b></li>
  1529. <li><b>type = musicvideo</b> generated events are: <b>album, artist, file</b> and <b>title</b></li>
  1530. <li><b>type = episode</b> generated events are: <b>episode, file, season, showtitle,</b> and <b>title</b></li>
  1531. <li><b>type = movie</b> generated events are: <b>originaltitle, file, title,</b> and <b>year</b></li>
  1532. <li><b>type = picture</b> generated events are: <b>file</b></li>
  1533. <li><b>type = unknown</b> generated events are: <b>file</b></li>
  1534. </ul>
  1535. </ul>
  1536. <br><br>
  1537. <a name="XBMCattr"></a>
  1538. <b>Attributes</b>
  1539. <ul>
  1540. <li>compatibilityMode<br>
  1541. This module can also be used to control Plex, since the JSON Api is mostly the same, but there are some differences.
  1542. If you want to control Plex set the attribute <i>compatibilityMode</i> to <i>plex</i>.</li>
  1543. <li>offMode<br>
  1544. Declares what should be down if the off command is executed. Possible values are <i>quit</i> (closes XBMC), <i>hibernate</i> (puts system into hibernation),
  1545. <i>suspend</i> (puts system into stand by), and <i>shutdown</i> (shuts down the system). Default value is <i>quit</i></li>
  1546. <li>fork<br>
  1547. If XBMC does not run all the time it used to be the case that FHEM blocks because it cannot reach XBMC (only happened
  1548. if TCP was used). If you encounter problems like FHEM not responding for a few seconds then you should set <code>attr &lt;XBMC_device&gt; fork enable</code>
  1549. which will move the search for XBMC into a separate process.</li>
  1550. <li>updateInterval<br>
  1551. The interval which is used to check if Kodi is still alive (by sending a JSON ping) and also it is used to update current player item.</li>
  1552. <li>disable<br>
  1553. Disables the device. All connections will be closed immediately.</li>
  1554. </ul>
  1555. </ul>
  1556. =end html
  1557. =cut