70_KODI.pm 61 KB

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